要须心地收汗马,孔孟行世目杲杲。这篇文章主要讲述ApplicationContext refresh 过程及一些重要的 processor 解析相关的知识,希望能为你提供帮助。
回顾上文 其实我们已经实现了一个简单的 BeanFactory
它具的功能有
- 注册 Bean 到容器,通过限定名获取 Bean
- 可以拦截 Bean 初始化前后的处理
- 可以在 Bean 属性注入后和即将销毁时做一些逻辑处理
- 解决了循环依赖
Bean 在 spring 中的完整生命周期,可以自行查看spring 的
BeanFactory
接口,它在最上面的注释有详细说明。【ApplicationContext refresh 过程及一些重要的 processor 解析】但实际的应用场景除了这个主要 Bean 管理外,还有一些消息广播、国际化、事件监听等
本文内容本文想分析下
ApplicationContext
的 refresh
过程,先来个总结性的过程,以便后续一堆枯燥的源码分析- 初始阶段:准备工作
- 第一步,我们先和得到一个
BeanFactory
,这里初始化的是DefaultListableBeanFactory
- 第二步,我们需要配置这个
BeanFactory
像 spel 表达式解析器、Environment
都要配置到工厂里面,以便后续的处理
- 第三步,我们允许添加
BeanFactoryProcessor
来修改BeanFactory
中的 Bean ,常用的子类BeanDefinitionRegistryPostProcessor
它可以往容器中添加 Bean 定义
像@Import、@PropertySource、@ComponentScan、@ImportResource、@Bean methods还有 tk.mybatis 都是靠它来注入自定义的 Bean 定义 的,不清楚什么是 bean 定义看我的 上篇文章
- 第五步,当然是 回调 上面收集到的所有的
BeanFactoryProcessor
了
- 第六步,回调所有收集到的
BeanPostProcessor
,这些processor
是已经加到容器中的,如果你看到这里,不懂什么是BeanPostProcessor
请回头看 上文 的 bean 的生命周期,不清楚回调那就没办法了,你还不够经验看这篇文章
- 第七步,国际化初始化(对于主体来说,这算是支线,因为大部分情况下我们都不是写的国际项目)
- 第八步,初始化事件广播器。事件:常见的如页面加载完成有 onLoad 事件,应用刚启动有 onLaunch 事件,项目中用得不多,可能是 spring 内部用得多吧
- 第九步,留一个回调给子类来初始化特别的 bean ,默认空的
- 第十步,注册监听器(所有实现了 ApplicationListener 的类),并把之前的一些事件发布一下
- 第十一步,基本上是单例实例化,基本上我们项目中的类都是在这个方法中实例化的
- 结束阶段:清理工作
- 熟悉 IOC 容器初始化的流程,可以把 spring 用得更好
- 一些像 spel 工具类性质的东西可以直接拿过来用,没必要引第三方库或重复造轮子
我们都知道,Bean 的生命周期中,可以添加
BeanPostProcessor
在 bean 的初始化前和初始化后做一些处理,同样的在 BeanFactory 也有一个 BeanFactoryPostProcessor
允许你在 BeanFactory 初始化后,修改BeanFactory。样例:
我们看一下
PropertyPlaceholderConfigurer
xml 配置时代的产物, 它的继承结构如下PropertyResourceConfigurer implements BeanFactoryPostProcessor
|-PlaceholderConfigurerSupport
|-PropertyPlaceholderConfigurer
我们通常会在最开始配置这个
<
context:property-placeholder location="classpath:jdbc.properties" />
PropertyPlaceholderConfigurer
用于对项目中的 @Value
值进行处理,把值注入进属性中,下面看下它如何实现的查看关键方法
PropertyResourceConfigurer.postProcessBeanFactory
,分为三步Properties mergedProps = mergeProperties();
// 从 location 中加载属性// Convert the merged properties, if necessary.
convertProperties(mergedProps);
//什么都没干,一个模板方法,可用于密码加密,自定义属性转换// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
// 真正处理属性的地方,解析 spel 表达式 ${}
真正的处理过程使用了访问者模式在
BeanDefinitionVisitor
中。springboot 又是怎么处理的呢,springboot 是固定了配置文件 application.properties ,而不再使用 location 来配置路径,它使用了
PropertySourcesPlaceholderConfigurer
来解析配置 ,它同样继承自 PlaceholderConfigurerSupport
并且重写了 postProcessBeanFactory 关键方法,在最后面使用了PropertySourcesPropertyResolver
进行了属性解析。使用方式可以在 这篇文章 看到
BeanDefinitionRegistryPostProcessor
继承自 BeanFactoryPostProcessor
它的参数是 BeanDefinitionRegistry
一个 Bean 定义 的注册器,它可以做些什么呢 ,查看它的方法,可以对 bean 定义做增删改查,厉害吧。registerBeanDefinition
removeBeanDefinition
getBeanDefinition
containsBeanDefinition
getBeanDefinitionNames
getBeanDefinitionCount
isBeanNameInUse
样例一:
在
ConfigurationClassPostProcessor
中,它实现了 BeanDefinitionRegistryPostProcessor
,它使用 ConfigurationClassParser
来解析项目中配置的 @Configuration
类,然后使用 ConfigurationClassBeanDefinitionReader
把解析到的类加载到 Bean 定义具体细节查看 这篇文章
样例二:
在 tkmybatis 的
MapperScannerConfigurer
中,也实现了 BeanDefinitionRegistryPostProcessor
,它使用 ClassPathMapperScanner
来扫描 Mapper 类,添加进容器public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
划重点
其实总结来说,refresh 的过程只是给你提供了一个大致的执行框架,具体的处理还是在一些 processor,listener 中,网上有大部分源码解读都拿这个 refresh 过程做大篇幅的讲解,其实没多大必要,就像 servlet 的生命周期一样,了解其执行过程,但剩下的处理都是子类去具现化的。
分享一篇说得不错的文章
BeanPostProcessor和BeanFactoryPostProcessor浅析以及在spring初始化中回调
一点小推广创作不易,希望可以支持下我的开源软件,及我的小工具,欢迎来 gitee 点星,fork ,提 bug 。
Excel 通用导入导出,支持 Excel 公式
博客地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi
使用模板代码 ,从数据库生成代码 ,及一些项目中经常可以用到的小工具
博客地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven
推荐阅读
- publish dotnet core angular spa app to docker
- uni-app自定义属性设置无法获取
- No such application config! Please add dubbo:application
- Android常见的加密算法
- nginx安装出现:cp: `conf/koi-win' and `/application/nginx-1.6.3/conf/koi-win' are the same file(示
- appium甯镐娇鐢ㄧ殑鍛戒护
- 动态扩容Linux根目录 (解决/dev/mapper/centos-root 占用了过高问题)
- java.lang.IllegalStateException: Failed to load property source from location 'classpath:/applic
- java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.base.BaseSelectProvider