Loading...
墨滴

Roy4259

2021/12/17  阅读:37  主题:嫩青

Spring IOC容器加载过程

IOC容器加载过程

Spring加载上下文的常用方式有xml、注解两种方式,xml的方式耦合度较高,我们使用注解的方式创建上下文

  1. 实例化容器
  2. 实例化Bean工厂
  3. 实例化BeanDefinition的读取器
  4. 实例化BeanDefinition的扫描器
  5. 将配置类注册为BeanDefinition
  6. 调用refresh()方法(仅展示关键方法)
    • invokeBeanFactoryPostProcessors(beanFactory)
    • finishBeanFactoryInitialization(beanFactory)

1. 实例化容器

查看AnnotationConfigApplicationContext的构造方法

2. 实例化Bean工厂

this() 方法会调用父类 GenericApplicationContext 的空构造方法 可以看到初始化了一个工厂类DefaultListableBeanFactory(功能最全的Bean工厂类)

3. 实例化 BeanDefinition 的读取器

回到AnnotationConfigApplicationContext的空构造方法 初始化了一个读取器:AnnotatedBeanDefinitionReader read,一个扫描器ClassPathBeanDefi nitionScanner scanner;scanner的用处不是很大,它仅仅是在我们外部手动调用 .scan 等方法才有用,常规方式是不会用到scanner对象的;

跟进 this.reader = new AnnotatedBeanDefinitionReader(this); 方法

跟进 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); 方法找到 registerAnnotationConfigProcessors 方法,发现里边会注册很多Bean定义(都是保证Spring IOC容器对应功能能正常运作的基础类),比如 ConfigurationClassPostProcessorAutowiredAnnotationBeanPostProcessor 由于代码过长这里只截取片段 看一下registerPostProcessor方法(会将该类的BeanDefinition放入到容器中,其他基础类的BeanDefinition也是如此被注册到容器中) 跟进到 registry.registerBeanDefinition(beanName, definition); 方法中,发现是一个接口类,查看他的实现类DefaultListableBeanFactory(还记得一开始调用构造方法初始化的那个工厂类吗,当然你也可以通过debug看看),方法比较长,看到下面关键的几行,将BeanDefinition放入了一个map中,beanName放入到一个list中(这两个属性beanDefinitionMapbeanDefinitionNames很常用)

4. 实例化 BeanDefinition 的扫描器

由于常规使用方式是不会用到 AnnotationConfigApplicationContext 里面的 scanner 的,这里的 scanner 仅仅是为了程序员可以手动调用 AnnotationConfigApplicationContext 对象的 scan 方法。所以这里就不看 scanner 是如何被实例化的了。

如下通过 context.scan("com.roy") 方法扫描包才会使用到这个扫描器,而通过 @ComponentScan("com.roy") 注解的方式会重新初始化一个扫描器进行扫描

小结

总结一下上面 this() 方法都做了什么:创建上下文对象AnnotationConfigApplicationContext的时候初始化了一个bean工厂类,创建读取器AnnotatedBeanDefinitionReader的时候注册了很多基础类的BeanDefinition;接下来会在后面实例化他们,并且调用他们的功能(比如加载配置类注册到我们容器中)


继续往下走,看到 register(componentClasses); 方法,这个方法将会把我们传入的配置类注册为 BeanDefinition

5. 将配置类注册为 BeanDefinition

深入 register(componentClasses); 这个方法,会循环注册传入的配置类 继续看 registerBean 方法,会进入到 doRegisterBean 方法中 1' AnnotatedGenericBeanDefinition 可以理解为描述 BeanDefinition 的类,它可以将传入的标记了注解的类转为 AnnotatedGenericBeanDefinition 类,调用 getMetadata 方法可以获得类上的注解;

2' conditionEvaluator.shouldSkip(abd.getMetadata()) 判断需不需要跳过注册,深入进去发现是检查 @Condition 注解,如果不满足条件,就会跳过这个类的注册;

3' 设置作用域,如果没有设置的话,默认为单例;

4' 获取 beanName;

5' AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); 解析通用注解,设置到 AnnotatedBeanDefinition 中,追踪进去发现解析的注解为 @Lazy,@Primary,@DependsOn,@Role,@Description;

6’ 处理限定符, 有其中 @Qualifier , @Primary , @Lazy 注解也需要设置到 AnnotatedGenericBeanDefinition abd 中;

7' 把 AnnotatedGenericBeanDefinition 数据结构和 beanName 封装到一个对象中;

8' BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry); 最终会调用 DefaultListableBeanFactory 中的 registerBeanDefinition 方法去注册,最终会放入到之前说到的beanDefinitionMapbeanDefinitionNames 中;

至此,传入的配置类已经被注册进了 Spring 容器中

6. 调用 refresh() 方法实例化 this() 方法中的基础类,然后调用他们的接口扫描包注册成 BeanDefinitionSpring 容器中

先了解 BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 两个接口.

BeanDefinitionRegistryPostProcessor 可以注册 beanDefinition BeanFactoryPostProcessor 可以修改已经注册的 beanDefinition 这两个接口会在 invokeBeanFactoryPostProcessors(beanFactory) 中被调用, this() 方法中创建的基础类, ConfigurationClassPostProcessor 就是同时实现继承了以上两个接口,他是spring中最重要的后置处理器,后面具体分析.

看到 refresh() 方法

对于IOC重点关注 invokeBeanFactoryPostProcessors(beanFactory);finishBeanFactoryInitialization(beanFactory); 两个方法即可

查看到 invokeBeanFactoryPostProcessors(beanFactory); 方法内,注意 getBeanFactoryPostProcessors() 是主动调用 addBeanFactoryPostProcessor 方法添加进来的 BeanFactoryPostProcessor

6.1 进入 invokeBeanFactoryPostProcessors 方法

/**
 *invokeBeanFactoryPostProcessors的整体流程: 
 *调用bean工厂的后置处理器
 * 1)BeanDefinitionRegistryPostProcessor(先被执行)
 *   所有的bean定义信息将要被加载到容器中,Bean实例还没有被初始化
 * 2)BeanFactoryPostProcessor(后执行)
 *   所有的Bean定义信息已经加载到容器中,但是Bean实例还没有被初始化.
 * 该方法的的作用就是用于ioc容器加载bean定义前后进行处理
 * BeanDefinitionRegistryPostProcessor是bean定义解析前调用
 *     1)实现了PriorityOrdered接口的
 *     2)实现了Ordered接口的
 *     3)没有实现任何的优先级接口的
 *     4)因为BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor接口的子接口,实现BeanFactoryPostProcessor的方法
 * BeanFactoryPostProcessor是bean定义解析后调用
 *     1)实现了PriorityOrdered接口的
 *     2)实现了Ordered接口的
 *     3)没有实现任何的优先级接口的
 */

1' 判断传入的工厂 beanFactory 是否有实现 BeanDefinitionRegistry ,实现了表示有注册 BeanDefinition 的能力, 需要先调用 BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry 方法, 再调用 BeanFactoryPostProcessor 接口方法 postProcessBeanFactory , 如果没有实现, 直接调用 BeanFactoryPostProcessor 接口方法 postProcessBeanFactory ;

    1. 循环手动创建传入进来的 beanFactoryPostProcessors (一般为空,需要 AnnotationConfigApplicationContext.addBeanFactoryPostProcessor 显示添加), 校验是否有实现 BeanDefinitionRegistry , 如果有, 调用他的 postProcessBeanDefinitionRegistry 方法, 然后加入到一个用于保存的 BeanDefinitionRegistryPostProcessor 的集合list registryProcessors 中(这个集合中的对象都是已经执行了 postProcessBeanDefinitionRegistry 的方法, 但是没有执行 postProcessBeanFactoryBeanDefinitionRegistry, 会在最后面统一调用执行 postProcessBeanFactory 方法), 如果没有实现, 会执行 regularPostProcessors.add(postProcessor); 加入到 BeanFactoryPostProcessor 的集合list regularPostProcessors 中;
    1. 定义一个list保存当前准备创建的 BeanDefinitionRegistryPostProcessor ,根据类型查找出 beanName ,依次找出实现了 PriorityOrdered , Ordered 以及没有实现任何优先级接口的 BeanDefinitionRegistryPostProcessor , 有实现优先级接口的需要排序,最后调用他们的 postProcessBeanDefinitionRegistry 方法,需要注意的是,根据类型找出 beanName 这个操作,每次都需要重新查询,因为 BeanDefinitionRegistryPostProcessor 是能创建 BeanDefinition 的;
    1. 调用完 postProcessBeanDefinitionRegistryregistryProcessors 集合以及自己传入的 regularPostProcessors 会调用 postProcessBeanFactory 方法。

2' 从容器中获取所有类型为 BeanFactoryPostProcessorbeanName, 依次找出实现了 PriorityOrdered , Ordered 以及没有实现任何优先级接口的 BeanFactoryPostProcessor, 实现优先级接口的需要排序,最后调用他们的 postProcessBeanFactory 方法。

大致扫完这个方法的整体流程,那么配置类(@Configuration@Component等)是怎么被加载到spring容器的呢,这离不开ConfigurationClassPostProcessor 这个类,它是扫描配置类并把配置注入到容器的关键,看到它实现了 BeanDefinitionRegistryPostProcessor 接口,而 BeanDefinitionRegistryPostProcessor 接口又继承了 BeanFactoryPostProcessor,所以 ConfigurationClassPostProcessor 会依次调用 postProcessBeanDefinitionRegistrypostProcessBeanFactory两个方法。 现在来分析ConfigurationClassPostProcessor中的方法

6.1.1 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

来看到ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法,会调用 processConfigBeanDefinitions 方法 1' 获取到IOC容器中当前所有的bean定义名称,放入candidateNames数组中;

2' 循环candidateNames数组,根据beanName拿到BeanDefinition,判断是否有处理过(根据某个属性判断,解析之后才会设置);

3' 判断是否是配置类,是的话,加入到configCandidates数组;在判断的时候,还会标记配置类属于Full配置类,还是Lite配置类,解释一下他们的区别;

1. 当注册配置类的时候,可以不加@Configuration注解,
直接使用@Component @ComponentScan @Import @ImportResource等注解,
Spring把这种配置类称之为Lite配置类, 
如果加了@Configuration注解,就称之为Full配置类。 
2. 如果注册了Lite配置类,getBean这个配置类,会发现它就是原本的那个配置类,
如果注册了Full配置类,getBean 这个配置类,会发现它已经不是原本那个配置类了,
而是已经被cgilb代理的类了。 
3. 写一个A类,其中有一个构造方法,打印出“你好”,再写一个配置类,
里面有两个被@bean注解的方法,其中一个方法new了A类,
并且返回A的对象,把此方法称之为getA,第二个方法又调用了getA方法,
如果配置类是Lite配置类,会发现打印了两次“你好”,
也就是说A类被new了两次,如果配置类是Full配置类,
会发现只打印了一次“你好”,也就是说A类只被new了一次,
因为这个类被cgilb代理了,方法已经被改写。

4' 如果candidateNames中为空(没有配置类),直接返回

5' 对candidateNames进行Order排序

6' 创建配置类解析器对象解析配置类,循环解析配置类 parser.parse(candidates);

    1. 查看 parse 方法,注解方式查看第一个 if 分支
    1. 查看调用链,发现配置类并没有直接被注册为 BeanDefinition ,而是加入到了一个 configurationClasses 的map中(在第七步统一注册成 BeanDefinition), 进入 doProcessConfigurationClass 看是如何解析的
      1. 处理 @PropertySource 注解, @PropertySource 注解用来加载 properties 文件
      1. 获得ComponentScan注解具体的内容,ComponentScan 注解除了最常用的 basePackage 之外,还有 includeFiltersexcludeFilters 等。
      1. 判断有没有被 @ComponentScans 标记,或者被 @Condition 标记,如果是的话
    1. 执行扫描操作,把扫描出来的放入set,这个方法稍后再详细说明。
    2. 循环set,判断是否是配置类,是的话,递归调用parse方法,因为被扫描出来的类,
    还是一个配置类,有@ComponentScans注解,或者其中有被@Bean标记的方法
    等等,所以需要再次被解析。

进入到doScan方法 把符合要求的文件,转换为BeanDefinition 最后面会将BeanDefinition注册到容器中(@ComponentScan会被马上注册到容器中,其它会在第七步(7')统一注册)

    1. 处理 @Import 注解
@Import有三种情况
①Import 普通类
②Import ImportSelector
③Import ImportBeanDefinitionRegistrar
1. 循环importCandidates,判断属于哪种情况
2.1 如果是普通类,会进到else,调用processConfigurationClass方法,
processImports这个方法就是在processConfigurationClass方法中被调用的
processImports又主动调用processConfigurationClass方法,
是一个递归调用,因为Import的普通类,也有可能被加了Import注解,
@ComponentScan注解或者其他注解,所以普通类需要再次被解析
如果Import ImportSelector就跑到了第一个if中去,
首先执行Aware接口方法,所以在实现ImportSelector的同时,
还可以实现Aware接口然后判断是不是DeferredImportSelector,
DeferredImportSelector扩展了ImportSelector
如果不是的话,调用selectImports方法,获得全限定类名数组,
在转换成类的数组,然后再调用processImports,又特么的是一个递归调用...
可能又有三种情况,一种情况是selectImports的类是一个普通类,
2.2 第二种情况是selectImports的类是一个ImportBean DefinitionRegistrar类,
2.3 第三种情况是还是一个ImportSelector类...所以又需要递归调用
如果Import ImportBeanDefinitionRegistrar就跑到了第二个if
还是会执行Aware接口方法,这里终于没有递归了,会把数据放到ConfigurationClass中的
Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars中去
    1. 处理 @ImportResource 注解
    1. 处理 @Bean 的方法,可以看到获得了带有 @Bean 的方法后,不是马上转换成 BeanDefinition ,而是先用一个set接收

7' this.reader.loadBeanDefinitions(configClasses); 统一注册 Bean

6.1.2 ConfigurationClassPostProcessor#postProcessBeanFactory

判断配置类是Lite配置类,还是Full配置类,如果是配置类,就会被Cglib代理

1' 判断当前后置处理器方法有没被调用过->没有

2' 判断是不是执行过processConfigBeanDefinitions方法->执行过不会重复执行(因为ConfigurationClassPostProcessor本身就有实现postProcessBeanDefinitionRegistry,在上方调用过,此处是用来处理懒加载的)

3' 使用cglib对配置类进行代理

只有full版配置类才会创建cglib代理
虽然在指定配置的时候不标注@Configuration也行,所以加不加注解的区别就在这里
加了@Configuration和不加有本质上有什么区别的?
当在配置类中一个@Bean 使用方法的方式引用另一个Bean如果不加注解就会重复加载Bean
如果加了@Configuration  则会在这里创建cglib代理,当调用@Bean方法时会先检测容器中是否存在

大概流程如下图 配置类是Lite还是Full这一步是在(6.1.1)调用方法 postProcessBeanDefinitionRegistryprocessConfigBeanDefinitions解析出来的,截取部分关键代码 跟踪进ConfigurationClassUtils.checkConfigurationClassCandidate() 方法

4' 添加 BeanPostProcessor 后置处理器:给代理类添加 BeanFactory 属性

6.2 进入 finishBeanFactoryInitialization 方法

这个方法是实例化所有剩余的(非懒加载)单例;
比如invokeBeanFactoryPostProcessors方法中根据各种注解解析出来的类,在这个时候都会被初始化; 
实例化的过程各种BeanPostProcessor开始起作用。 
这个方法是用来实例化懒加载单例Bean的

简单的画了个图,涉及到bean的生命周期,后面再详细分析

Roy4259

2021/12/17  阅读:37  主题:嫩青

作者介绍

Roy4259