Spring Bean的生命周期(二) - BeanDefinition

我们开始来研究 bean 完整生命周期的 BeanDefinition 阶段了,这一阶段主要发生了以下几件事情:

  • 加载配置文件、配置类。

  • 解析配置文件、配置类并封装为 BeanDefinition

  • 编程式注入额外的 BeanDefinition

  • BeanDefinition 的后置处理。

1. 加载xml配置文件

来,跟着小册来到 LifecycleSourceXmlApplication 的测试代码:

public static void main(String[] args) throws Exception {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
    ctx.setConfigLocation("lifecycle/bean-source.xml");
}

1.1 保存配置文件路径

这里 xml 配置文件的加载会使用 ClassPathXmlApplicationContextsetConfigLocation 方法,点进去可以发现它只是在 AbstractRefreshableConfigApplicationContext 中,给 configLocations 设置了配置文件的路径存放而已。

通过源码,也能看出来最终是将传入的路径切分,并顺序存入 configLocations 中:

public void setConfigLocation(String location) {
    // 切分配置文件路径
    setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
}

public void setConfigLocations(@Nullable String... locations) {
    if (locations != null) {
        // assert ......
        this.configLocations = new String[locations.length];
        for (int i = 0; i < locations.length; i++) {
            // 存入ApplicationContext中
            this.configLocations[i] = resolvePath(locations[i]).trim();
        }
    }
    else {
        this.configLocations = null;
    }
}

Debug 时,也能发现,它最终把测试代码中设置的 xml 配置文件的路径都保存了。

img

1.2 加载配置文件并解析

当执行 ApplicationContextrefresh 方法后,会开始刷新(初始化)IOC 容器,这里面有 13 个步骤,前面已经看过不少次了:

这里面我把第 2 步 obtainFreshBeanFactory 方法标注出来了,加载配置文件并解析的动作就在这里面,我们可以来研究一下。

这个方法只有两个动作:刷新 BeanFactory ,然后获取它。毫无疑问,刷新的动作中包含配置文件的加载和解析,我们继续往里看。

refreshBeanFactory 方法是 AbstractApplicationContext 定义的抽象方法,很明显这里又是模板方法模式的体现了。由于当前我们正在研究的是基于 xml 配置文件的 ApplicationContext ,所以要进入 AbstractRefreshableApplicationContext 中:

看一下源码的大体流程,在前面 15 章中我们已经了解到,基于 xml 配置文件的 **ApplicationContext** 可以反复刷新加载 IOC 容器,所以此处有已经存在的判断:如果当前 ApplicationContext 中组合的 BeanFactory 已经存在,则销毁原来的 BeanFactory ,并重新创建。

关注重点,这里面加载配置文件的动作是 loadBeanDefinitions 。这个方法相当复杂且困难,小册会选取最重要的部分来解析。

1.3 loadBeanDefinitions

点进去,发现 loadBeanDefinitions 又是一个抽象方法,在 AbstractXmlApplicationContext 中可以找到对应的实现:

注意这里面,它创建了一个 XmlBeanDefinitionReader ,小伙伴们一定不陌生吧,它在第 14 章就出现过,我们说它就是加载和解析 xml 配置文件的核心 API 。直接看最底下吧,它把这个 XmlBeanDefinitionReader 作为参数传入重载的 loadBeanDefinitions 方法:

注意这段逻辑分两部分,一个是处理已经加载好的现成的 Resource ,一个是处理指定好的配置文件资源路径。由于测试代码中并没有直接指定 Resource ,故此处主要研究第 8 行的 loadBeanDefinitions(configLocations)

Debug 至此处,同样也能发现 readerconfigLocations 都准备好了:

img

1.4 XmlBeanDefinitionReader加载配置文件

点进来,发现它传入的是一组配置文件,那自然就会循环一个个的加载咯。循环自然不是重点,我们进入内部的 loadBeanDefinitions(location) 中:

可以发现,这里的核心逻辑只有两个动作:1) 根据传入的资源路径,获取 xml 配置文件2) 解析 xml 配置文件。其余的动作都是保证程序正常执行的代码,咱就不研究了,还是核心关注 loadBeanDefinitions 的方法实现。

这一步给原有的 xml 配置文件的 Resource 封装包装了一层编码而已,没啥需要关注的,继续往里看。

方法逻辑看上去很复杂,但是抛开细枝末节,最核心的动作还是那个以 do 开头的 doLoadBeanDefinitions ,它才是真正干活的,所以看它就完事了。

1.5 doLoadBeanDefinitions - 读取配置文件

一上来又是一个 do 开头的方法,得了,我就看你。。。诶等一下,doLoadDocument 这很明显是解析文档吧,这我们也没必要看吧(也看不懂),重点是下面的那句 registerBeanDefinitions ,这才是我们最关心的吧!

注意,此处构造了一个 DefaultBeanDefinitionDocumentReader ,然后调用它的 registerBeanDefinitions 方法(不再是 XmlBeanDefinitionReader 的方法了):

又又又是这个套路!xxx 方法最终调 doXxx 方法干活,得了那咱继续往里走:

注意看这段源码,上面它会先把 xml 配置文件中声明的 profile 取出来,并根据 Environment 中配置好的 profile 决定是否继续解析( profile 的过滤).

接下来,后面就是 xml 配置文件的解析动作了,在这前后有一个预处理和后处理动作,不过默认情况下这里是没有实现的(模板方法罢了),所以我们只需要看 parseBeanDefinitions 就可以。

1.6 parseBeanDefinitions - 解析xml

Spring 如何解析 xml ,我们不关心,但是如何从解析完的 xml 中获取关键信息,以及封装 **BeanDefinition** ,这才是我们要关注的。

这个方法一进来的时候,我们在此 Debug 停一下,可以发现它已经是解析并封装成 Element 的形式了,而且根节点是 <beans> ,它还有几个默认属性等等,我们都比较熟悉了:

img

OK ,继续回到源码,源码中可以看到,每次循环出来的 Node 都会尝试着转成 Element 去解析,而解析的动作主要是 parseDefaultElement ,它会解析 <beans> 标签下的 xml 元素。马上就要水落石出了,我们点进去看:

终于看到真实的面貌了!!!这里它会解析 <import> 标签、<alias> 标签、<bean> 标签,以及递归解析嵌套的 <beans> 标签!

1.7 processBeanDefinition - 解析 <bean> 标签

既然我们还是在研究 BeanDefinition ,那我们就研究 processBeanDefinition 方法啦,翻开源码,可以发现逻辑还是比较简单的:

可以发现,整个动作一气呵成,其中第一个步骤会把 xml 元素封装为 BeanDefinitionHolder ,且不说 holder 是干嘛用,只从名字上就能知道,它内部肯定组合了一个 BeanDefinition

Debug 至此,可以发现,这个 Element 中已经包含了一个 <bean> 标签中定义的那些属性了:

img

不过源码是如何将 xml 元素封装为 BeanDefinitionHolder 的呢?我们还是进去看看为好。

虽说源码篇幅比较长,但总体还是比较容易理解和接受的,而且每一步干的活也都容易看明白。不过看完这段源码之后,小伙伴们可能会疑惑:只有 idnameclass 呢?scope 呢?lazy-init 呢?莫慌,看到中间还有一个封装好的 parseBeanDefinitionElement 方法了吧,这些属性的封装都在这里可以找得到,我们继续往里进。同样的,这段源码篇幅较长,只需要关注带注释的即可:

从这部分,我们终于看到了最最底层的操作:调用 createBeanDefinition 方法创建了一个 BeanDefinition (它的底层就是第 24 章提到的 BeanDefinitionReaderUtils.createBeanDefinition 方法),之后把 <bean> 标签中的其它属性、<bean> 的子标签的内容都封装起来,而封装 <bean> 其它属性的 parseBeanDefinitionAttributes 方法中,已经把这些内容都解析到位了:

到这里,xml 配置文件中的 <bean> 标签就可以转换为 BeanDefinition 了。

1.7.1 BeanDefinitionHolder的意义

最后补充一下前面略过的 BeanDefinitionHolder ,我们说它是持有 BeanDefinition 的一个包装而已,不过它除了持有之外,还包含了另外的重要信息:bean 的名称

翻看 BeanDefinitionHolder 的源码结构,可以发现这里面还组合了 bean 的 name 和 alias :

是不是突然回过神了,BeanDefinition 的结构中是没有 name 的!所以才需要这样一个 holder 帮它持有。

1.8 小结

简单总结一下这个流程吧:首先 **ClassPathXmlApplicationContext** **refresh** 之前,会指定传入的 xml 配置文件的路径,执行 **refresh** 方法时,会初始化 **BeanFactory** ,触发 xml 配置文件的读取、加载和解析。其中 xml 的读取需要借助 **XmlBeanDefinitionReader** ,解析 xml 配置文件则使用 **DefaultBeanDefinitionDocumentReader** ,最终解析 xml 中的元素,封装出 **BeanDefinition** ,最后注册到 **BeanDefinitionRegistry**

2. 加载注解配置类

相比较于 xml 配置文件,注解配置类的加载时机会晚一些,它用到了一个至关重要的 BeanDefinitionRegistryPostProcessor ,而且无论如何,这个后置处理器都是最最优先执行的,它就是 **ConfigurationClassPostProcessor**

下面我们还是通过实例来研究注解配置类的加载机制(注意此处要换用 LifecycleSourceAnnotationApplication 了哦)。

2.1 BeanDefinitionRegistryPostProcessor的调用时机

还是回到 AbstractApplicationContextrefresh 方法中,这次我们要看的是 BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessor 的执行时机,而它们的执行都在下面所示的第 5 步 invokeBeanFactoryPostProcessors 中执行:

进来这个方法,发现核心的方法就一句话:

好吧,它又是交给一个 Delegate 执行,那就进去看这个方法的实现吧。

2.2 PostProcessorRegistrationDelegate的实现

前方高能invokeBeanFactoryPostProcessors 方法的篇幅实在太长了!!!小册只截取关键的部分吧。

用简单的语言概括,这个方法的执行机制如下:

  1. 执行 BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry 方法

    1. 执行实现了 PriorityOrdered 接口的 BeanDefinitionRegistryPostProcessor

    2. 执行实现了 Ordered 接口的 BeanDefinitionRegistryPostProcessor

    3. 执行普通的 BeanDefinitionRegistryPostProcessor

  2. 执行 BeanDefinitionRegistryPostProcessorpostProcessBeanFactory 方法 同上

  3. 执行BeanFactoryPostProcessorpostProcessBeanFactory 方法 同上

这个方法的更详细解读,可参考《SpringBoot 小册》第 12 章 5.1 节,那里面解释的更加详细呦。

在这个长长的方法中,第一个环节它会执行所有实现了 PriorityOrdered 接口的 BeanDefinitionRegistryPostProcessor ,这里面第一个执行的处理器就是 ConfigurationClassPostProcessor ,咱跟过去看一看。

2.3 ConfigurationClassPostProcessor的处理

直接定位到 postProcessBeanDefinitionRegistry 方法吧:

可以发现,在这个方法的最后一行,就是解析配置类中定义的 bean ,并封装为 BeanDefinition

进入超难领域前的 “按摩” 提醒:processConfigBeanDefinitions 方法的内容超级复杂哦(不亚于上面的 xml 配置文件加载),考虑到前面的 SpringBoot 小册中对该部分流程没有全部剖析boot 的小册中对于 IOC 更注重整体应用和容器的生命周期),所以我们在这里完整的研究一遍配置类的加载原理

进入 processConfigBeanDefinitions 方法,来吧,超级长的源码又出现了:

其实如果小伙伴从上往下看的话,应该可以意识到,其实也没那么难吧(当然没那么难,难的在 parseloadBeanDefinitions 里头)。不过这里面的前置动作还是要注意一下,它初始化了一个 ConfigurationClassParser ,这个家伙是用来解析注解配置类的核心 API 。

一大堆前戏都做足了,下面就可以进入 ConfigurationClassParserparse 方法了。

2.4 ConfigurationClassParser#parse - 解析注解配置类

先整体看一下这个方法的内容哈。上面的 for 循环中,它会把配置类的全限定名拿出来,扔进重载的 parse 方法中(注意无论是执行 if-else-if 的哪个分支,最终都是执行重载的 parse 方法);for 循环调用完成后,最底下会让 deferredImportSelectorHandler 执行 process 方法,这个东西我们完全没见过,这里有必要说明一下。

2.4.1 ImportSelector的扩展

在 SpringFramework 4.0 中,ImportSelector 多了一个子接口:DeferredImportSelector ,它的执行时机比 ImportSelector 更晚,它会在注解配置类的所有解析工作完成后才执行(其实上面的源码就已经解释了这个原理)。

一般情况下,DeferredImportSelector 会跟 @Conditional 注解配合使用,完成条件装配

2.4.2 deferredImportSelectorHandler的处理逻辑

进入 DeferredImportSelectorHandlerprocess 方法:

可以发现这个处理逻辑还是很简单的,它会取出所有解析中存储好的 DeferredImportSelector ,并依次执行。由于 DeferredImportSelector 的执行时机比较晚,对于 @Conditional 条件装配的处理也会更有利,所以这个设计还是不错的。

2.5 parse解析配置类

回到正题上,上面的 ConfigurationClassParser 中最终都会把配置类传入重载的 parse 方法中,参数类型注意是 ConfigurationClass

好吧,底下又是调 **doXXX** 方法了,那就废话不多说,直接莽进去就完事了。

2.6 doProcessConfigurationClass - 解析配置类

又高能!这个方法又是好长呀!不过这里面的部分几乎都有大用途哦,我们分解着来看吧。

2.6.1 处理@Component注解

一上来,它就会判断这个类是否有标注 @Component 注解。因为所有的 @Configuration 类必定是 @Component ,所以该逻辑必进。而内部执行的 processMemberClasses 方法如下:

哦 ~ 合计着这个方法是处理内部类的啊,而且还是递归处理,这个套路很像上面 <beans> 的 xml 递归解析哦。那好吧,这里面的逻辑也不复杂,扫一眼过去就得了,不要浪费太多时间。

2.6.2 处理@PropertySource注解

接下来是处理 @PropertySource 注解了,可以发现借助 AnnotationConfigUtils 可以很容易的取出配置类上标注的所有注解信息,然后筛选出指定的注解属性即可。而内部的 processPropertySource 方法就在真正的封装 PropertySource 导入的资源文件:

前面的一大堆操作都是拿 @PropertySource 的一些属性等等,最后的 for 循环中才是封装资源文件,存放进 Environment 的部分,整体逻辑也是比较简单的,咱就不深入研究咯(主要关注 bean 相关的东西哈)。

2.6.3 处理@ComponentScan注解

接下来的部分是处理 @ComponentScan 注解了,整个流程也不复杂。注意一点:@ComponentScan 可以标注多个,并且 Spring 4.3 后多了一个 @ComponentScans 注解,它可以组合多个 @ComponentScan 注解,所以这里是 for 循环解析 @ComponentScan

中间的部分,它使用 ComponentScanAnnotationParser 来委托处理包扫描的工作,可能会有小伙伴产生疑惑:不是包扫描的组件是 ClassPathBeanDefinitionScanner 吗?它是谁?先别着急,我们进到 ComponentScanAnnotationParserparse 方法中,看一看内部的实现:(嗯,第一行就把 ClassPathBeanDefinitionScanner 创建出来了 ~ )

顺序走下来,大概这个方法只干了三件事情:

  1. 构造 ClassPathBeanDefinitionScanner ,并封装 @ComponentScan 注解中的属性

  2. 整理要进行包扫描的 basePackages ,以及 include 和 exclude 的过滤器

  3. 执行包扫描的动作

前面两个步骤都是准备动作,真正的包扫描那还得看 ClassPathBeanDefinitionScanner ,来吧,咱直接进到最底下的 doScan 方法中:

从上往下的逻辑条理还是很清晰的,只要扫描到了符合的类(默认被 @Component 注解标注的类),就会包装为 BeanDefinition ,然后对这些 BeanDefinition 进行一些额外的处理,最终注册进 BeanDefinitionRegistry 。不过核心的扫描方法还是封装方法了,咱进入 for 循环的第一句 findCandidateComponents 方法中:

整体逻辑还是简单的很:它会将带有通配符的 Ant 风格(诸如 /xxx/**/*.class )的路径解析出来,并加载到对应的类,封装为 ScannedGenericBeanDefinition ,完事。

到这里,包扫描的处理就完成了,@ComponentScan 的处理也就结束了。

2.6.4 处理@Import注解

嚯,就一句话啊,那咱直接点进去吧:

可以发现,整体逻辑非常有序,它分别对 ImportSelectorImportBeanDefinitionRegistrar 、普通类 / 配置类都做了处理,并递归解析其中存在的配置类,整体并不复杂,小伙伴们看一看有个印象即可,重要的是记住它们处理的位置,以便日后出现问题时排错定位。

2.6.5 处理@ImportResource注解

前面我们学过了,在注解配置类上标注 @ImportResource 可以导入 xml 配置文件,而解析这些 @ImportResource 的逻辑就在这里。不过看源码的套路中,怎么只是把配置文件的路径放入了 configClass 而已呢?咋不解析呀?莫慌,我们之前看 ConfigurationClassPostProcessor 的后置处理 processConfigBeanDefinitions 方法中,不是在 parse 方法下面还有个 loadBeanDefinitions 方法嘛,过会我们看看它就知道了。

2.6.6 处理@Bean注解

合着 @Bean 的处理也只是存起来呗?那它啥时候处理啊?不会跟上面的 @ImportResource 放在一块处理吧?哎,想法可以保留,过会我们往下看到了自然就揭晓了。不过它是如何把这些 @Bean 方法都拿出来的,我们还得去看看 retrieveBeanMethodMetadata 方法:

一上来我们就看到 @Bean 注解的方法过滤了,经过这个过滤之后,就只会得到配置类中被 @Bean 注解标注的方法。

Debug 到这里,可以发现此处确实把配置类中的 person() 方法取了出来:

img

不过这里可能会有小伙伴产生疑惑:明明反射就可以拿到这些被 @Bean 注解标注的方法,但是中间的读取部分都把 ASM (读取字节码的技术)搬出来了,为什么要如此的大动干戈呢?哎,这里我们要多解释一个点了:**使用 JVM 的标准反射,在不同的 JVM 、或者同一个 JVM 上的不同应用中,返回的方法列表顺序可能是不同的。**简言之,JVM 的标准反射不保证方法列表返回的顺序一致。所以,想要保证程序在任何 JVM 上、任何应用中,加载同一个 .class 文件的方法列表都返回相同的顺序,那就只能读取字节码了,而读取字节码的技术,Spring 选择了 ASM 。

所以,这里 Spring 使用 ASM 读取字节码的目的,是为了保证加载配置类中 **@Bean** 方法的从上到下的顺序与源文件 .java 中一致

2.6.7 处理父接口

又是一个一句话方法,咱点进去看一看:

注意看这段逻辑,它会把配置类实现的所有接口都拿出来,并且遍历所有标注了 @Bean 的方法,并添加到 bean 的注册信息中。跟上面一样,它只是存起来,并没有封装为 BeanDefinition ,所以这里只是解析动作而已。

2.6.8 父类的返回

终于看到最后一部分了,因为上面的解析处理逻辑都看完了,还剩下最后一个部分。如果配置类存在父类的话,父类也应该一起加载,所以这里会取到配置类的父类,并继续递归处理。逻辑相对的不算复杂,咱就不深入研究了。

2.7 loadBeanDefinitions - 加载BeanDefinition

到这里,配置类的解析就完成了,回到 ConfigurationClassPostProcessor 中,解析完那些 @Bean 后还要注册为 BeanDefinition 呢,而这个方法的核心在 loadBeanDefinitions 中,我们也来研究。

this.reader.loadBeanDefinitions(configClasses); 的执行会来到 ConfigurationClassBeanDefinitionReader 中:

又是循环,来吧,直接进到 loadBeanDefinitionsForConfigurationClass 中:

这里面的逻辑分为 4 个部分,咱还是分别来看。

2.7.1 registerBeanDefinitionForImportedConfigurationClass

第一个步骤是将配置类自身注册进 BeanFactory 。按照包扫描的原则来看,只要是 @Configuration ,那就必然也是 @Component ,就应该一起注册到 BeanFactory 中。但如果配置类是通过 @Import 的方式导入的,那就不会主动将自己注册进 BeanFactory ,所以在这里,它需要将那些被 @Import 进去的配置类,也全部注册到 BeanFactory 中。

从源码实现上看,这就是一个最最普通的 BeanDefinition 的注册,没有任何额外的花里胡哨的操作,所以这个我们也没必要深查了,看一下就好咯。

2.7.2 loadBeanDefinitionsForBeanMethod

这个步骤就是加载刚才处理过的那些 @Bean 方法了,咱来看看它怎么处理。

一样标准的套路:检查 → 构造 BeanDefinition → 封装信息 → 注册进 BeanDefinitionRegistry 。最下面的部分,那些处理 @Bean 的属性、额外的注解解析都比较简单了,小伙伴们可以自行借助 IDE 去看看,小册就不展开解释了。

不过这里面有一个还蛮关键的部分,要给小伙伴们解释一下,就是上面的这个 metadata.isStatic() ,它的判断逻辑下面,会给 BeanDefinition 封装两个属性:setBeanClassName / setFactoryBeanNamesetUniqueFactoryMethodName ,它们俩分别指定了当前 @Bean 方法所在的配置类,以及方法名。小伙伴们可以先猜想一下,封装它的作用是什么呢?

回想一下前面 @Component 注解标注的类形成的 BeanDefinition ,以及 xml 配置文件转换出来的 BeanDefinition 有个什么特点?它们都指定了 bean 的全限定名、属性注入等,而且最终创建的对象一定是通过反射创建。而在注解配置类中的 @Bean 方法是有实际的代码执行,属于编程式创建,无法使用(也不适合用)反射创建 bean 对象,所以为了在后面能正常创建出 bean 对象,此处就需要记录该 bean 的定义源(包含注解配置类和方法名),以保证在创建 bean 对象时,能够使用反射调用该注解配置类的方法,生成 bean 对象并返回

2.7.3 loadBeanDefinitionsFromImportedResources

这部分是解析从注解配置类上取到的 xml 配置文件的路径,有了前面的分析,我们马上就能猜到,它又要用 XmlBeanDefinitionReader 那一套来搞了,点开源码,发现果然如此:

很容易理解了吧,里面的套路想必不用小册多解释,小伙伴们自己就已经有很清楚的认识了吧。

2.7.4 loadBeanDefinitionsFromRegistrars

最后一部分是执行 ImportBeanDefinitionRegistrar ,这个就更简单了,既然是接口,那执行它们的话,只需要调用 registerBeanDefinitions 方法就可以吧,进到方法内部,发现真就是这么简单:

OK ,那到此为止,注解配置类中 BeanDefinition 的注册也就全部理清楚了,顺便我们把整个注解配置类的解析流程和逻辑也都研究了一遍,小伙伴们要做好笔记呀。

2.8 小结

同样总结一下注解配置类的加载与解析过程:注解配置类的解析发生在 **BeanDefinitionRegistryPostProcessor** 的执行阶段,它对应的核心后置处理器是 **ConfigurationClassPostProcessor** ,它主要负责两个步骤三件事情:解析配置类、注册 **BeanDefinition** 。三件事情包括:1) 解析 **@ComponentScan** 并进行包扫描,实际进行包扫描的组件是 **ClassPathBeanDefinitionScanner** ;2) 解析配置类中的注解(如 **@Import** **@ImportResource** **@PropertySource** 等)并处理,工作的核心组件是 **ConfigurationClassParser** ;3) 解析配置类中的 **@Bean** 并封装 **BeanDefinition** ,实际解析的组件是 **ConfigurationClassBeanDefinitionReader**

3. BeanDefinition的后置处理

执行完 ConfigurationClassPostProcessor 之后,在 xml 和配置类中定义的 BeanDefinition 就都解析和准备好了,但是还没有加载进 BeanDefinitionRegistry 中。下面还会有 BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 的执行,而它们的执行时机还是上面的 PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors 方法中。

至于这些处理的机制,咱在前面的 28 章 BeanFactoryPostProcessor 章节都讲解过了,如果忘记的小伙伴们记得回去复习哦。

执行完这些后置处理器之后,BeanDefinition 的后置处理也就算结束了。

4. 总结

首先,bean 的生命周期分为 **BeanDefinition** 阶段和 bean 实例阶段。

BeanDefinition 阶段分为加载 xml 配置文件、解析注解配置类、编程式构造 BeanDefinitionBeanDefinition 的后置处理,一共四个部分。

  1. 加载 xml 配置文件 发生在基于 xml 配置文件的 ApplicationContextrefresh 方法的 BeanFactory 初始化阶段,此时 BeanFactory 刚刚构建完成,它会借助 XmlBeanDefinitionReader 来加载 xml 配置文件,并使用 DefaultBeanDefinitionDocumentReader 解析 xml 配置文件,封装声明的 <bean> 标签内容并转换为 BeanDefinition

  2. 解析注解配置类 发生在 ApplicationContextrefresh 方法的 BeanDefinitionRegistryPostProcessor 执行阶段,该阶段首先会执行 ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry 方法。ConfigurationClassPostProcessor 中会找出所有的配置类,排序后依次解析,并借助 ClassPathBeanDefinitionScanner 实现包扫描的 BeanDefinition 封装,借助 ConfigurationClassBeanDefinitionReader 实现 @Bean 注解方法的 BeanDefinition 解析和封装。

  3. 编程式构造 **BeanDefinition** 也是发生在 ApplicationContextrefresh 方法的 BeanDefinitionRegistryPostProcessor 执行阶段,由于 BeanDefinitionRegistryPostProcessor 中包含 ConfigurationClassPostProcessor ,而 ConfigurationClassPostProcessor 会执行 ImportBeanDefinitionRegistrar 的逻辑,从而达到编程式构造 BeanDefinition 并注入到 BeanDefinitionRegistry 的目的;另外,实现了 BeanDefinitionRegistryPostProcessor 的类也可以编程式构造 BeanDefinition ,注入 BeanDefinitionRegistry

最后更新于

这有帮助吗?