Spring IOC容器的生命周期
前面我们已经研究了 Bean 的完整生命周期,在此期间我们也对整个 IOC 容器的生命周期了解了部分流程。本章咱来完整的看一下 IOC 容器的一个刷新动作中,会有哪些动作的触发,以及从这些流程中,我们能梳理出哪些可以切入进 SpringFramework 内部流程的扩展点。
本章主要涉及的原理部分:
ApplicationContext#refresh
方法整体概述refresh
方法中可以切入的扩展点
好了我们开始,前面我们已经反复多次提到 ApplicationContext
的 refresh
方法是整个 IOC 容器初始化的核心,它一共分为 13 步:
1. AbstractApplicationContext#refresh
本章我们重点研究的,是这里面每一步都做了哪些事情,对于里面涉及的细节不作过多研究,感兴趣的小伙伴可以移步 boot 小册第 11 - 15 章。
考虑到部分方法的实现中涉及到的源码和注释会很多,会很影响阅读体验,小册只会截取关键步骤的关键源码进行解读。
1.1 prepareRefresh - 初始化前的预处理
这一步中,大多数的动作都是前置性的准备,而且基本上这些准备的动作在我们现阶段学习的内容中几乎都用不到,小伙伴们可以暂且忽略即可。
initPropertySources
方法在基于 web 环境的 ApplicationContext
子类中有重写,在 boot 小册的第 11 章中有提及到,感兴趣的小伙伴们可以先去看一看。
关于早期事件的概念,可能现阶段解释起来比较复杂,后续再考虑补充。
1.2 obtainFreshBeanFactory - 初始化BeanFactory
基于 xml 配置文件的 ApplicationContext
中,在该步骤会解析 xml 配置文件,封装 BeanDefinition
。但是,基于注解配置类的 ApplicationContext
就不太一样了,上一章中我们看到,GenericApplicationContext
的 refreshBeanFactory
方法中,有一个 CAS 判断的动作,它控制着 GenericApplicationContext
不能反复刷新。所以从这一步我们能知道的,是基于 xml 配置文件的 **ApplicationContext**
可以反复刷新,基于注解配置类的 **ApplicationContext**
只能刷新一次。
1.3 prepareBeanFactory - BeanFactory的预处理动作
这个方法比较长,但仔细观察可以发现源码是非常有条理的:
这部分很好理解吧!不过这个方法内信息量有点大,我们有必要研究一下。
1.3.1 ApplicationContextAwareProcessor的注册
在 Aware
接口注入的后置处理器中,那个起核心作用的 ApplicationContextAwareProcessor
是在此处注册的,而且它负责注入的 6 个 Aware
接口,恰好就是上面源码中忽略的 6 个接口,也证实了 ApplicationContextAwareProcessor
接管 BeanFactory
自动注入这一设计。
如何理解这一设计:BeanFactory
来自 spring-beans
包,而 ApplicationContext
来自 spring-context
包。由于 context
依赖 beans
,而 beans
本身可以单独存在。在没有 context
包的环境下,BeanFactory
本身也可以作为一个普通的 IOC 容器来处理依赖查找和自动类型注入,而引入 context
包后,出现了更多的内部组件,注入的要求也就更复杂。本着单一职责与职责分离的原则,BeanFactory
还是干原来的事情,扩展的 Aware
回调注入则交给 context
包中的 ApplicationContextAwareProcessor
搞定。
1.3.2 ignoreDependencyInterface的设计
既然有 6 个 Aware
回调接口的工作被 ApplicationContextAwareProcessor
接管了,那 BeanFactory
本身就不再需要考虑这 6 个接口的注入了。这 6 个接口最终会存储到 AbstractAutowireCapableBeanFactory
的 ignoredDependencyInterfaces
变量中:
而它被利用的时机,是在 bean 生命周期中的属性赋值 populateBean
阶段:
这个源码片段是不是很熟悉呀,在第 35 章中,我们讲解 applyPropertyValues
方法,就是截取了这一小段,而上面露头的 filterPropertyDescriptorsForDependencyCheck
方法,内部就使用了 ignoredDependencyInterfaces
这个集合检查那些依赖的 Aware
接口。
这里多说一嘴 Aware
接口在没有被 BeanFactory
忽略掉的执行原则吧。通常来讲,Aware
接口都会定义一个 setXXX
的方法(如 BeanFactoryAware
接口的 setBeanFactory
方法),同时实现了 Aware
接口的方法通常都会在成员属性中定义一个与回调注入类型相同的属性变量(如实现了 BeanFactoryAware
接口的类,通常都会定义一个 private BeanFactory beanFactory;
)。如果是按照这种设计编写的代码,BeanFactory
会认定这个属性可以受自动注入的支持( autowire-mode
),并对这个属性进行依赖注入。简单的讲,**BeanFactory**
可以支持的自动注入的属性必须带有 setter 方法,而 **Aware**
接口的方法定义风格刚好是 setter 的风格,所以只需要求实现了 **Aware**
接口的类,定义属性时,属性名与 setter 方法对应的名称一致即可( setPerson → person )。
1.3.3 registerResolvableDependency的设计
这个 registerResolvableDependency
方法,可能小伙伴们看了源码之后会更容易理解:
是不是一下子就看明白了,调用这个方法后,在 BeanFactory
再遇到特定类型的属性注入时,会直接从这个 resolvableDependencies
的 Map
中找出对应的值,直接注入进去(相当于预先指定好了哪个类型注入哪个值,不需要额外考虑)。
1.4 postProcessBeanFactory - BeanFactory的后置处理
这个方法在 AbstractApplicationContext
中是一个模板方法,它的重写在基于 web 环境的 ApplicationContext 子类中有实现,回头我们学到 WebMvc 章节中再来看它。
1.5 invokeBeanFactoryPostProcessors - 执行BeanFactoryPostProcessor
这个方法我们前面已经看过了,它会执行所有的 BeanDefinitionRegistryPostProcessor
和 BeanFactoryPostProcessor
,主要工作是包扫描、解析配置类等,不再赘述。
1.6 registerBeanPostProcessors - 初始化BeanPostProcessor
这个方法我们前面也看过了,它会把所有的 BeanPostProcessor
都注册到位,这里面的处理逻辑与上面的 invokeBeanFactoryPostProcessors
类似,忘记的小伙伴们记得看前面第 34 章哦。
1.7 initMessageSource - 初始化国际化组件
这个方法的源码小册不在此处贴出,它涉及到国际化的知识。早在第 15 章我们就说过,国际化的内容小册会放到 WebMvc 章节中讲解,so 此处先留下一个坑,回头到了 WebMvc 中再填。
1.8 initApplicationEventMulticaster - 初始化事件广播器
这部分的内容非常简单了,它会构造一个 SimpleApplicationEventMulticaster
,并注册进 BeanFactory
中,仅此而已。
1.9 onRefresh - 子类扩展的刷新动作
该部分又是一个模板方法,而它的扩展在纯 Spring 环境下是找不到的,不过在 SpringBoot 中,有子类对这个方法的重写,感兴趣的小伙伴可以移步 boot 小册第 16 章学习。
1.10 registerListeners - 注册监听器
这个方法共分为三个小段,条理也非常清晰。不过要注意一点,此处那些没有初始化的 ApplicationListener
并没有被实例化,而是会等到下一步 finishBeanFactoryInitialization
方法中才会被创建出来(注释中也说了,不要在此处创建,因为框架希望让那些后置处理器去干预它们)。
1.11 finishBeanFactoryInitialization - 初始化剩余的单实例bean
这个方法就是一个一个的循环初始化那些非延迟加载的单实例 bean 了,整个流程我们已经在第 34 、35 章讲过了,这里不再赘述。
1.12 finishRefresh - 刷新后的动作
这个方法小册在第 35 章也讲到过了,它主要是初始化 LifecycleProcessor
、广播刷新完成的事件,整体也没什么难的。
1.13 resetCommonCaches - 清除缓存
这就没啥好说的了吧,这些乱七八糟的缓存都可以干掉了,因为准备工作都完成了。
1.14 小结
纵观整个 refresh
方法,每个动作的职责都很清晰,而且非常的有条理性。这个过程中,有对 BeanFactory
的处理,有对 ApplicationContext
的处理,有处理 BeanPostProcessor
的逻辑,有准备 ApplicationListener
的逻辑,最后它会初始化那些非延迟加载的单实例 bean 。整个 refresh
方法走下来,ApplicationContext
也就全部初始化完毕了。
2. ApplicationContext初始化中的扩展点
看了上面的整个逻辑,想必小伙伴们也比较清楚的理解 ApplicationContext
中的初始化逻辑了。IOC 原理的最后一个部分,我们来梳理一下,整个 ApplicationContext
的初始化逻辑中,都有哪些扩展点可供我们切入利用。清楚地理解这些扩展点,可以让我们在后续扩展 Spring 时更加容易和游刃有余。
一般情况下,我们不会在 ApplicationContext
的初始化和 refresh
动作之间作太多的处理,主要还是从 refresh
方法本身出发考虑,故以下梳理的扩展点都来自于 refresh
方法开始触发时。
2.1 invokeBeanFactoryPostProcessors
前四个方法中,在普通的 ApplicationContext
下都无法切入,所以只能在第 5 步 invokeBeanFactoryPostProcessors
方法中切入了。而这个方法中可供切入的点实在是太多了,咱一一来数。
2.1.1 ImportSelector&ImportBeanDefinitionRegistrar
咦,没想到吧,第一个竟然不是 BeanFactoryPostProcessor
或者BeanDefinitionRegistryPostProcessor
,原因是它们的执行时机通常都在 ConfigurationClassPostProcessor
之后啦,而 **ConfigurationClassPostProcessor**
的执行过程中,会解析 **@Import**
注解,取出里面的 **ImportBeanDefinitionRegistrar**
并执行,所以第一个扩展点是 ImportSelector
和 ImportBeanDefinitionRegistrar
了。
ImportSelector
在该阶段只能拿到当前 @Import
标注的注解配置类的信息(如下面代码中 BarImportSelector
只能拿到 BarConfiguration
的信息)
ImportBeanDefinitionRegistrar
在该阶段除了可以获取到当前 @Import
标注的注解配置类的信息之外,更重要的是能拿到 BeanDefinitionRegistry
,由此可供扩展的动作主要是给 BeanDefinitionRegistry
中注册新的 BeanDefinition
。
不过小伙伴如果把 BeanDefinitionRegistry
看做 DefaultListableBeanFactory
也不是不行,只是说考虑到依赖倒转的设计,此处还是拿接口比较合适。
2.1.2 BeanDefinitionRegistryPostProcessor
这个切入算是比较简单明了的吧,前面我们也已经学过了,使用 BeanDefinitionRegistryPostProcessor
可以拿到 BeanDefinitionRegistry
的 API ,直接向 IOC 容器中注册新的 BeanDefinition
。
不过这里面有一点要注意,刚才上面也提到了,一般情况下,自定义的 **BeanDefinitionRegistryPostProcessor**
执行时机比内置的 **ConfigurationClassPostProcessor**
要晚,这也是 SpringFramework 最开始的设计( ConfigurationClassPostProcessor
实现了 PriorityOrdered
接口,这个接口的优先级最高)。
注意上面的话说的是 “一般情况下” 哦,说明小册有留台阶的。如果小伙伴们非要让自己写的 BeanDefinitionRegistryPostProcessor
执行时机比 ConfigurationClassPostProcessor
早,可以让后置处理器实现 **PriorityOrdered**
接口,声明较高执行优先级(不能是 **Ordered.LOWEST_PRECEDENCE**
,否则排序规则会变成字母表顺序)。经过这样的设计之后,自定义的 BeanDefinitionRegistryPostProcessor
就可以在 ConfigurationClassPostProcessor
之前执行了。
2.1.3 BeanFactoryPostProcessor
BeanFactoryPostProcessor
的切入时机紧随 BeanDefinitionRegistryPostProcessor
之后,本来它没什么好说的了,小伙伴们只需要注意一下,在 BeanFactoryPostProcessor
的切入回调中,可以拿到的参数是 ConfigurableListableBeanFactory
,拿到它就意味着,我们在这个阶段按理来讲不应该再向 BeanFactory
中注册新的 BeanDefinition
了,只能获取和修改现有的 BeanDefinition
。
另外,还要注意一点,javadoc 中有说,BeanFactoryPostProcessor
的处理阶段是可以提早初始化 bean 对象的,因为这个阶段下只有 ApplicationContextAwareProcessor
注册到了 BeanFactory
中,没有其余关键的 BeanPostProcessor
,所以这个阶段初始化的 bean 有一个共同的特点:能使用 **Aware**
回调注入,但无法使用 **@Autowired**
等自动注入的注解进行依赖注入,且不会产生任何代理对象。
2.2 registerBeanPostProcessors
所有 BeanFactoryPostProcessor
和 BeanDefinitionRegistryPostProcessor
都处理完之后,下一步是初始化 BeanPostProcessor
。这个逻辑我们之前也都看过了,但这个阶段没有可以切入影响该阶段的时机,所以该阶段跳过。
2.3 finishBeanFactoryInitialization
一下子就来到最复杂的初始化 bean 的这一步了。这里面的切入时机才多呢,前面看 bean 的实例化和初始化过程中,有非常多可供切入的时机,我们逐个来看。
本方法中涉及到的所有切入点均为针对单个 bean 的扩展。
2.3.1 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
从 bean 的创建阶段之前,就有 InstantiationAwareBeanPostProcessor
来拦截创建了,每个 bean 在创建之前都会尝试着使用 InstantiationAwareBeanPostProcessor
来代替创建,如果没有任何 InstantiationAwareBeanPostProcessor
可以拦截创建,则会走真正的 bean 对象实例化流程。
在 InstantiationAwareBeanPostProcessor
的 postProcessBeforeInstantiation
方法中,只能拿到 bean 对应的 Class 类型,以及 bean 的名称(当然啦,本来就是凭空造,有 Class 类型就够了),如果方法没有返回值,则代表 InstantiationAwareBeanPostProcessor
不参与拦截 bean 创建的动作。
2.3.2 SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors
如果在实例化 bean 之前,InstantiationAwareBeanPostProcessor
没有起到作用,就会通过构造器创建对象。如果一个 bean 有多个构造器,如何选择合适的构造器去创建对象就是很重要的一步。在 34 章 2.4.4 节我们看到,筛选构造器的核心方法是 determineConstructorsFromBeanPostProcessors
,它会在底层寻找所有 SmartInstantiationAwareBeanPostProcessor
,回调 determineCandidateConstructors
方法获取可选择的构造器:
在第 27 章中我们就说过这个 SmartInstantiationAwareBeanPostProcessor
的使用了,如果在这里打入 Debug 断点时,程序代码运行时可以停在断点,但不会有任何返回,这个现象的产因是:默认情况下 ConfigurationClassPostProcessor
会向 IOC 容器注册一个 ImportAwareBeanPostProcessor
,但它又没有重写 determineCandidateConstructors
方法,就造成了这个现象。
所以一般情况下,SmartInstantiationAwareBeanPostProcessor
在 SpringFramework 内部是用不到的,我们平时开发不是特殊场景也用不上,这个小伙伴们知道一下就可以了。
2.3.3 MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
在 createBeanInstance
方法执行完毕之后,此时 bean 对象已经创建出来了,只是没有任何属性值的注入而已。此时 doCreateBean
方法会走到 applyMergedBeanDefinitionPostProcessors
方法,让这些 MergedBeanDefinitionPostProcessor
去收集 bean 所属的 Class 中的注解信息。在第 35 章中小册有列出三个关键的 MergedBeanDefinitionPostProcessor
,它们分别是 InitDestroyAnnotationBeanPostProcessor
(收集 @PostConstruct
与 @PreDestroy
注解)、CommonAnnotationBeanPostProcessor
(收集 JSR 250 的其它注解)、AutowiredAnnotationBeanPostProcessor
(收集自动注入相关的注解)。
在此处切入扩展,意味着可以对 bean 对象所属的 Class 作一些处理或者收集的动作(当然也可以进行属性赋值等动作,但考虑到职责分离,该步骤还是不要瞎搞为好 ~ ~)。
2.3.4 InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
等这些 MergedBeanDefinitionPostProcessor
都工作完毕后,此时 bean 对象所属的 Class 中的信息都收集完毕了,接下来还得让 InstantiationAwareBeanPostProcessor
出场,它要负责控制是否继续走接下来的 populateBean
和 initializeBean
方法初始化 bean 。
所以如果在这里切入扩展的话,只能做到流程控制的作用。
2.3.5 InstantiationAwareBeanPostProcessor#postProcessProperties
又又又又又是它!不过这次它调用的是 postProcessProperties
方法,这个步骤会将 bean 对象对应的 PropertyValues
中封装赋值和注入的数据应用给 bean 实例。通常情况下,在该阶段 SpringFramework 内部起作用的后置处理器是 AutowiredAnnotationBeanPostProcessor
,它会搜集 bean 所属的 Class 中标注了 @Autowired
、@Value
、@Resource
等注解的属性和方法,并反射赋值 / 调用。
在此处扩展逻辑的话,相当于扩展了后置处理器的属性赋值 + 依赖注入的自定义逻辑。当这个动作执行完毕之后,就不会再有属性赋值和组件注入的回调了。
2.3.6 BeanPostProcessor
接下来的两个动作就发生在 initializeBean
方法中了,它就是 BeanPostProcessor
的前后两个执行动作 postProcessBeforeInitialization
和 postProcessAfterInitialization
。进入到 initializeBean
方法后,bean 的生命周期已经到了初始化逻辑回调的阶段,此时 bean 中应该注入的属性均已完备,BeanPostProcessor
的切入也只是给 bean 添加一些额外的属性的赋值、回调等等,以及生成代理对象。
所以,在此处切入扩展逻辑,相当于针对一个接近完善的 bean 去扩展 / 包装。当后置处理器执行完 postProcessAfterInitialization
方法后,基本上就代表 bean 的初始化结束了。
2.3.7 SmartInitializingSingleton
这个扩展在第 35 章我们有说过,它来自 SpringFramework 4.1 ,它是在所有非延迟加载的单实例 bean 全部初始化完成后才回调的。这个阶段底层会取出所有实现了 SmartInitializingSingleton
接口的 bean ,去回调 afterSingletonsInstantiated
方法。而且当时小册还说了,这个设计只是为了让 BeanFactory
也能插一脚初始化的后处理,仅此而已。
这个扩展是针对单个 bean 的, 不是切入所有 bean 的,所以严格意义上讲它属于 bean 初始化的扩展点。不过话又说回来,它是在 BeanFactory
把单实例 bean 都初始化完成后统一回调的,又属于一个整体动作,所以小册也就把它拿出来了。
最后更新于