当前位置:   article > 正文

Spring bean加载过程解析(上)_the absolute path within the class path

the absolute path within the class path

Spring 作为 Ioc 框架,实现了依赖注入,由一个中心化的 Bean 工厂来负责各个 Bean 的实例化和依赖管理。各个 Bean 可以不需要关心各自的复杂的创建过程,达到了很好的解耦效果。

我们对 Spring 的工作流进行一个粗略的概括,主要为两大环节:

  • 解析,读 xml 配置,扫描类文件,从配置或者注解中获取 Bean 的定义信息,注册一些扩展功能。
  • 加载,通过解析完的定义信息获取 Bean 实例。

 

Spring总体流程

1.准备数据阶段

关于spring容器管理Bean的过程以及加载模式

1.需要将bean的定义信息声明在spring的配置文件中(注解和xml都可以)

2.需要通过spring抽象出的各种Recourse来指定对应的配置文件

1.1关于Recource类的简述

1.1.1 ClassPathResource构造方法解析

Spring的Resource接口旨在成为一个更有能力的接口,用于抽象对低级资源的访问。

获取到了Resource对象也就等于获取到了该资源文件,后面可以根据方法的定义对文件进行相关操作

//创建一个ClassPathResource,先导的斜杠/会被删除掉,因为ClassLoader资源访问方法不会接受它。

  1. public ClassPathResource(String path) {
  2. this(path, (ClassLoader) null);
  3. }

 

  1. /**
  2. * Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
  3. * A leading slash will be removed, as the ClassLoader resource access
  4. * methods will not accept it.
  5. * @param path the absolute path within the classpath
  6. * @param classLoader the class loader to load the resource with,
  7. * or {@code null} for the thread context class loader
  8. * @see ClassLoader#getResourceAsStream(String)
  9. */
  10. public ClassPathResource(String path, ClassLoader classLoader) {
  11. //断言 类路径不能为空
  12. Assert.notNull(path, "Path must not be null");
  13. //判断路径是否合法,清理路径相关的方法,不必过多的关注,可以简单的看看替换符号,长度的判断等
  14. String pathToUse = StringUtils.cleanPath(path);
  15. //移除/
  16. if (pathToUse.startsWith("/")) {
  17. pathToUse = pathToUse.substring(1);
  18. }
  19. //得到可以使用的path
  20. this.path = pathToUse;
  21. //赋值ClassLoader,如果没出传递,那么调用ClassUtils.getDefaultClassLoader()获取类加载器,这就是spring的强大之处,各种回退的策略,尽可能的保证程序的正常运行
  22. this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
  23. }

1.1.1.1 构造方法中ClassLoader的获取: ClassUtils.getDefaultClassLoader());

对于常规的使用方式我们返回的其实都是系统类加载器 ,但是不排除我们使用特殊的手段;

  1. this.getClass.getClassLoader();
  2. Thread.currentThread().getContextClassLoader();
  3. 方法一得到的Classloader是静态的,表明类的载入者是谁;方法二得到的Classloader是动态的,谁执行(某个线程),就是那个执行者的Classloader。对于单例模式的类,静态类等,载入一次后,这个实例会被很多程序(线程)调用,对于这些类,载入的Classloader和执行线程的Classloader通常都不同。
  4. 还需要更加详细的介绍和理解

 

  1. /**
  2. * Return the default ClassLoader to use: typically the thread context
  3. * ClassLoader, if available; the ClassLoader that loaded the ClassUtils
  4. * class will be used as fallback.
  5. * <p>Call this method if you intend to use the thread context ClassLoader
  6. * in a scenario where you clearly prefer a non-null ClassLoader reference:
  7. * for example, for class path resource loading (but not necessarily for
  8. * {@code Class.forName}, which accepts a {@code null} ClassLoader
  9. * reference as well).
  10. * @return the default ClassLoader (only {@code null} if even the system
  11. * ClassLoader isn't accessible)
  12. * @see Thread#getContextClassLoader()
  13. * @see ClassLoader#getSystemClassLoader()
  14. */
  15. public static ClassLoader getDefaultClassLoader() {
  16. ClassLoader cl = null;
  17. try {
  18. //Thread类提供的一个重要方法,获取当前线程的上下文的类加载
  19. //线程类加载器的重要性在于可以在运行期动态的改变类加载器的一个类加载的方式,我们可以先set一个类加载器,后续通过get方式拿出来
  20. cl = Thread.currentThread().getContextClassLoader();
  21. }
  22. catch (Throwable ex) {
  23. //获取线程类加载器失败,回退
  24. // Cannot access thread context ClassLoader - falling back...
  25. }
  26. //没有线程类加载器,使用加载了ClassUtils(当前类)的类加载器
  27. if (cl == null) {
  28. // No thread context class loader -> use class loader of this class.
  29. cl = ClassUtils.class.getClassLoader();
  30. //c1==null并不代表发生类异常,如果c1==null其实代表的是加载ClassUtils类的类加载器是bootstrap类加载器,我们可以通过一些手段来做到使用bootstrap classloader
  31. if (cl == null) {
  32. // getClassLoader() returning null indicates the bootstrap ClassLoader
  33. try {
  34. //获取到系统类加载器
  35. cl = ClassLoader.getSystemClassLoader();
  36. }
  37. catch (Throwable ex) {
  38. // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
  39. }
  40. }
  41. }
  42. return cl;
  43. }

1.1.2 ClassPathResource构造方法总结

构造方法执行结束,我们初始化了ClassPathResource类,主要是给path属性赋值和classLoader属性赋值; 为什么要这么做呢

  1. private final String path;
  2. @Nullable
  3. private ClassLoader classLoader;// 通过ClassLoader加载资源文件
  4. @Nullable
  5. private Class<?> clazz; // 通过Class类加载资源文件
  6. // 通过类路径创建resource
  7. public ClassPathResource(String path){...}
  8. // 通过类路径和给定的ClassLoader创建resource
  9. public ClassPathResource(String path, @Nullable ClassLoader classLoader){...}
  10. // 通过类路径和给定的Class类创建resource
  11. public ClassPathResource(String path, @Nullable Class<?> clazz){...}
  12. // 通过类路径和给定的ClassLoader或Class创建resource
  13. protected ClassPathResource(String path, @Nullable ClassLoader classLoader, @Nullable Class<?> clazz){...}

1.1.2.1获取的资源的流对象

  1. 1 @Test
  2. 2 public void testClassPathResource() throws IOException {
  3. 3 Resource res = new ClassPathResource("resource/ApplicationContext.xml");
  4. 4 InputStream input = res.getInputStream();
  5. 5 Assert.assertNotNull(input);
  6. 6 }

1.1.2.2 三个构造法方法的总结

  1. 1 public ClassPathResource(String path) {
  2. 2 this(path, (ClassLoader) null);
  3. 3 }
  1. 1 public ClassPathResource(String path, ClassLoader classLoader) {
  2. 2 Assert.notNull(path, "Path must not be null");
  3. 3 String pathToUse = StringUtils.cleanPath(path);
  4. 4 if (pathToUse.startsWith("/")) {
  5. 5 pathToUse = pathToUse.substring(1);
  6. 6 }
  7. 7 this.path = pathToUse;
  8. 8 this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
  9. 9 }
  1. 1 public ClassPathResource(String path, Class<?> clazz) {
  2. 2 Assert.notNull(path, "Path must not be null");
  3. 3 this.path = StringUtils.cleanPath(path);
  4. 4 this.clazz = clazz;
  5. 5 }

1.1.2.3获取资源和构造方法之间的关系

  1. 1 /**
  2. 2 * This implementation opens an InputStream for the given class path resource.
  3. 3 * @see java.lang.ClassLoader#getResourceAsStream(String)
  4. 4 * @see java.lang.Class#getResourceAsStream(String)
  5. 5 */
  6. 6 @Override
  7. 7 public InputStream getInputStream() throws IOException {
  8. 8 InputStream is;
  9. 9 if (this.clazz != null) {
  10. //调用Class类的底层方法
  11. 10 is = this.clazz.getResourceAsStream(this.path);
  12. 11 }
  13. 12 else if (this.classLoader != null) {
  14. //inputStream 还是调用底层的方法 其实Class类还是比较难的一个类
  15. 13 is = this.classLoader.getResourceAsStream(this.path);
  16. 14 }
  17. 15 else {
  18. 16 is = ClassLoader.getSystemResourceAsStream(this.path);
  19. 17 }
  20. 18 if (is == null) {
  21. 19 throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
  22. 20 }
  23. 21 return is;
  24. 22 }

源码解读:

该类获取资源的方式有两种:Class获取ClassLoader获取

两种方法的区别

在创建ClassPathResource对象时,我们可以指定是按Class的相对路径获取文件还是按ClassLoader来获取。 在高版本的spring中呢既有clazz又有classLoader的构造方法已经被标注为废弃了,也就是说spring为我们提供了一种二选一的方式来加载我们的资源,其实加载资源的最底层还是通过Class类来实现或者通过Classloader类来loadRecource

1.2beanFactory类以及DefaultListableBeanFactory简述

第三步:.需要显式声明一个spring工厂 用来掌控我们的bean以及他们之间的相互依赖

关于DefaultListBeanFactory的简述

1.2.1DefaultListableBeanFactory构造方法简介

   DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();

1.2.1.1DefaultListableBeanFactory的无参数构造方法()

  1. /**
  2. * Create a new DefaultListableBeanFactory.
  3. */
  4. public DefaultListableBeanFactory() {
  5. //调用父类的构造方法,调用初始化方法并不代表会生成对象,你的java代码中出现new关键字加上构造方法的调用,只会生成一个对 象,其父类对象不会生成。
  6. super();
  7. }

1.2.1.2抽象父类AbstractAutowireCapableBeanFactory的构造方法

  1. /**
  2. * Create a new AbstractAutowireCapableBeanFactory.
  3. */
  4. public AbstractAutowireCapableBeanFactory() {
  5. super(); //一直掉到BeanFactory接口的构造方法;我也不明白为啥要一直调用
  6. ignoreDependencyInterface(BeanNameAware.class);
  7. ignoreDependencyInterface(BeanFactoryAware.class);
  8. ignoreDependencyInterface(BeanClassLoaderAware.class);
  9. }

ignoreDependencyInterface(BeanNameAware.class);

ignoreDependencyInterface的主要功能是忽略给定接口的自动装配功能,也就是当有忽略的接口类,自动装配会忽略这部分类的初始化装配,因为某种情况下,此时的接口实现类不能初始化,例如BeanNameAware,要想装配这个接口的实现对象,可以实现这个接口,通过实现的set方法进行装配。

 

如:当 A类 中有属性 B,当 Spring 在获取 A 的 Bean 的时候如果属性 B 还没有被初始化,Spring 就会自动初始化 B,(这也是Spring的一个重要特性)。但是,某些情况下,B 不会被初始化,比如 B 实现了 BeanNameAware 接口。Spring API介绍:应用程序上下文通常使用它来注册以其他方式解析的依赖项,如通过BeanFactoryAware实现的BeanFactory或通过ApplicationContextAware实现的ApplicationContext。默认情况下,只忽略BeanFactoryAware接口。若要忽略其他类型,请为每个类型调用此方法。

 

为什么要进行忽略呢?

简单的说:由于我要创建一个BeanFactory。但是这个过程中我的Bean可能会依赖一些接口,如:BeanNameAware等。这些接口在创建Bean的过程中不会去实例化,而是自动忽略掉这些依赖。为什么要忽略这些依赖呢?

  private final Set<Class<?>> ignoredDependencyInterfaces = new HashSet<Class<?>>();

这货其实是个Set集合。目前我们存在两个问题没有搞清楚

 

1 为什么在实现某些接口的时候依赖的接口要自动忽略注入?

2 如何实现忽略注入的?(待续) 这个还不知道

关于第一个问题:对于Spring自动创建Bean,但是Bean是无状态的,也就是说Bean不知道Spring容器BeanFactory的任何信息,包括Bean自己的名称name,Spring这样做的目的是为了Spring容器和Bean的解耦带来的问题就是Bean的无状态。那么Bean要想定制化的做一些操作,就必然要获取BeanFactory中的信息,在Spring Bean的生命周期中我们都知道实现一些列接口去观察Bean创建过程中的一些信息。这里的BeanNameAware、BeanfactoryAware、BeanClassLoaderAware这些接口就是获取Bean的名称、BeanFactory的信息以及类加载器的信息的。==因此这里牵扯到Spring创建bean的方式,正常实例化的bean和对定制化的bean要有所区分,因此Spring对正常实例化的Bean就要忽略这个依赖注入放入接口。

  1. public void ignoreDependencyInterface(Class<?> ifc) {
  2. this.ignoredDependencyInterfaces.add(ifc);
  3. }

BeanNameAware.class

  1. public interface BeanNameAware extends Aware {
  2. void setBeanName(String name);
  3. }

XXXAware在spring里表示对XXX可以感知,通俗点解释就是:

如果在某个类里面想要使用spring的一些东西,就可以通过实现XXXAware接口告诉spring,spring看到后就会给你送过来,而接收的方式是通过实现接口唯一的方法set-XXX。

比如,有一个类想要使用当前的ApplicationContext,那么我们只需要让它实现ApplicationContextAware接口,然后实现接口中唯一的方法void setApplicationContext(ApplicationContext applicationContext)就可以了,spring会自动调用这个方法将applicationContext传给我们,我们只需要接收就可以了!很方便吧!

1.3BeanDefinitionReader的简介

1.3.1BeanDefinitionReader简介:

BeanDefinitionReader 的作用是读取 Spring 配置文件中的内容,将其转换为 IoC 容器内部的数据结构:BeanDefinition。

BeanDefinitionReader 一般可以使用一个 BeanDefinitionRegistry(我们的ListAbleBeanFacotry实现了这个接口,能提供注册bean的方式) 构造,然后通过 loadBeanDefinitions()等方法,把 Resources 转化为多个 BeanDefinition 并注册到 BeanDefinitionRegistry。

  1. package org.springframework.beans.factory.support;
  2. import org.springframework.beans.factory.BeanDefinitionStoreException;
  3. import org.springframework.core.io.Resource;
  4. import org.springframework.core.io.ResourceLoader;
  5. public interface BeanDefinitionReader {
  6. //返回Bean工厂以向其注册Bean定义。
  7. BeanDefinitionRegistry getRegistry();
  8. // // 这主要用于从bean定义资源中导入其他资源,例如,在xml中定义的bean的“ import”标记。
  9. ResourceLoader getResourceLoader();
  10. ClassLoader getBeanClassLoader();
  11. BeanNameGenerator getBeanNameGenerator();
  12. int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
  13. int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
  14. int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
  15. int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
  16. }
  1. /*
  2. *4.需要定义一个配置信息读取器 来读取配置文件中的信息并将读取之后的信息装配到我们之前的声明的工厂之中**
  3. */
  4. BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);

1.3.2AbstractBeanDefinitionReader

该类是实现了 BeanDefinitionReader 和 EnvironmentCapable 接口的抽象类,提供常见属性:工作的 bean 工厂、资源加载器、用于加载 bean 类的类加载器、环境等。

1.3.2.1AbstractBeanDefinitionReader简介

  1. public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {
  2. /** Logger available to subclasses */
  3. protected final Log logger = LogFactory.getLog(getClass());
  4. private final BeanDefinitionRegistry registry;
  5. private ResourceLoader resourceLoader;
  6. private ClassLoader beanClassLoader;
  7. private Environment environment;
  8. private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();

1.3.2.2AbstractBeanDefinitionReader 构造方法

  1. protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
  2. Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
  3. this.registry = registry;
  4. // Determine ResourceLoader to use.
  5. //还是回退的策略 尽可能保证我们的spring能正确执行
  6. if (this.registry instanceof ResourceLoader) {
  7. this.resourceLoader = (ResourceLoader) this.registry;
  8. }
  9. else {
  10. this.resourceLoader = new PathMatchingResourcePatternResolver();
  11. }
  12. // Inherit Environment if possible 环境的概念
  13. if (this.registry instanceof EnvironmentCapable) {
  14. this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
  15. }
  16. else {
  17. this.environment = new StandardEnvironment();
  18. }
  19. }

关于该类最核心的方法是 loadBeanDefinitions()方法,所以接下来我们主要就是分析该方法。

  1. public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
  2. Assert.notNull(locations, "Location array must not be null");
  3. int count = 0;
  4. String[] var3 = locations;
  5. int var4 = locations.length;
  6. for(int var5 = 0; var5 < var4; ++var5) {
  7. String location = var3[var5];
  8. count += this.loadBeanDefinitions(location);
  9. }
  10. return count;
  11. }

当传入的参数为资源位置数组时,进入上述方法,如果为字符串数组,则挨个遍历调用 loadBeanDefinitions(location)方法。其定义如下:

  1. public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
  2. return this.loadBeanDefinitions(location, (Set)null);
  3. }
  4. public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
  5. //获取资源加载器,该ResourceLoader是ResourcePatternResolver
  6. ResourceLoader resourceLoader = this.getResourceLoader();
  7. if (resourceLoader == null) {
  8. throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
  9. } else {
  10. int count;
  11. if (resourceLoader instanceof ResourcePatternResolver) {
  12. try {
  13. //根据资源路径调用resourceLoader的getResources方法,该方法以前在ResourceLoader一节讲过,此方法可以加载多个资源
  14. Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
  15. //根据资源来加载bean定义
  16. count = this.loadBeanDefinitions(resources);
  17. if (actualResources != null) {
  18. Collections.addAll(actualResources, resources);
  19. }
  20. if (this.logger.isTraceEnabled()) {
  21. this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
  22. }
  23. return count;
  24. } catch (IOException var6) {
  25. throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var6);
  26. }
  27. } else {
  28. //此方法只能加载一个资源
  29. Resource resource = resourceLoader.getResource(location);
  30. count = this.loadBeanDefinitions((Resource)resource);
  31. if (actualResources != null) {
  32. actualResources.add(resource);
  33. }
  34. if (this.logger.isTraceEnabled()) {
  35. this.logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
  36. }
  37. return count;
  38. }
  39. }
  40. }

根据资源加载器的不同,来处理资源路径,从而返回多个或一个资源,然后再将资源作为参数传递给 loadBeanDefinitions(resources)方法。在该类中存在一个 loadBeanDefinitions(Resource... resources)方法,该方法用于处理多个资源,归根结底,最后还是调用 loadBeanDefinitions((Resource)resource)方法,该方法的具体实现在 XmlBeanDefinitionReader 中。

1.3.2.2Environment 环境

我们可以把Environment理解

Environment直译为环境,是PropertyResolver的子接口。这两个接口,共同规定了一些关于系统运行环境配置的设置、获取等相关的方法,要求其实现类来实现。

换句话说,EnvironmentCapable,要求其实现类,具备和当前系统运行环境交互的能力。事实上,能够和当前系统环境交互,也是一个web应用(甚至其他应用),需要具备的基本能力。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/66374
推荐阅读
相关标签
  

闽ICP备14008679号