javascript
beanfactorypostprocessor_Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)
之前的文章我写了BeanDefinition的基本概念和合并,其中很对次提到了容器的扩展点,这篇文章就写这方面的知识。这部分的内容主要涉及到官网的1.8小节。按照官网介绍来说,容器的扩展点可以分为三类,BeanPostProcessor,BeanFactoryPostProcessor以及FactoryBean。本文主要讲BeanFactoryPostProcessor,对应官网的1.8.2小节
总览:
先看官网怎么说:
从上面这段话可以总结如下几点:
接下来,我们通过demo来感受下BeanFactoryPostProcessor的作用
例子:
这里以官网上的demo为例:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="locations" value="classpath:com/something/jdbc.properties"/> </bean><bean id="dataSource" destroy-method="close"class="org.apache.commons.dbcp.BasicDataSource"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/> </bean>实际值来自标准Java Properties格式的另一个文件:
# jdbc.properties jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root在上面的例子中,我们配置了一个PropertyPlaceholderConfigurer,为了方便理解,我们先分析下这个类,其UML类图如下:
- Ordered用于决定执行顺序
- PriorityOrdered这个接口直接继承了Ordered接口,并且没有做任何扩展,知识作为一个标记接口,也用于决定BeanFactoryPostProcessor的执行顺序。在后文源码分析时能看到它的作用
- Aware相关的接口以后我在接受Bean的生命周期回调时再同一分析
- FunctionalInterface是Java8新增的一个接口,也只是起一个标记的作用,标记该接口是一个函数式接口
- PropertiesLoaderSupport这个类主要包含定义了属性的加载方法,包含的属性如下:
- PropertyResourceConfigurer这个类主要可以对读取到的属性进行一些转换
- PlaceholderConfigurerSupport主要负责对占位符进行解析。其中几个属性如下:
- PropertyPlaceholderConfigurer继承了上面这些类的所有功能,同时可以配置属性的解析顺序:
对这个类有一些了解后,我们回到之前的例子中,为什么在jdbc.properties文件配置的属性值会被应用到BasicDataSource这个Bean呢。我画个图:
这个流程图就如上图,可以看到我们通过PropertyPlaceholderConfigurer这个特殊的BeanFactoryPostProcessor完成了BeanDefinition中属性值中的占位符替换。在BeanDefinition被解析处理后,Bean实例化之前对其进行了更改。
在上图中,创建bean的过程我们暂且不管,还有一个问题需要弄清楚,Spring是如何扫描并解析成BeanDefinition呢?这里就不得不提接下来需要分析的接口:BeanDefinitionRegistryPostProcessor
org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor:
先看这个接口的UML类图:
从上图可以得出两个结论:
我们来看下这个接口定义
BeanDefinitionRegistryPostProcessor:
BeanFactoryPostProcessor:
相比于正常的BeanFactoryPostProcessor,BeanDefinitionRegistryPostProcessor多提供了一个方法,那么多提供的这个方法有什么用呢,会在什么时候执行呢,先说结论:
这个方法的作用也是为了扩展,相比于BeanFactoryPostProcessor的postProcessBeanFactory方法,这个方法的执行时机会更靠前,Spring自身利用这个特性完成了BeanDefinition的扫描注解。我们对Spring进行扩展时,也可以利用这个特性来完成扫描功能。比如最新版的mybatis就是这么做的。关于mybatis和Spring的整合,我打算在写完Spring的扫描以及容器的扩展点这一系列文章后单独用一篇文章进行分析。
接下来我们直接分析其源码,验证上面的结论。
执行流程源码分析:
在分析源码前,我们看看下面这个图,方便大家对Spring的执行流程有个大概的了解:
上图表示的是AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class)的执行流程。我们这次分析的代码主要是其中3-5-1流程。代码比较长,拆分为两步:
BeanDefinitionRegistryPostProcessor执行流程:
BeanFactoryPostProcessor执行流程:
......承接上半部分代码......
通过源码分析,我们可以将整个Bean工厂的后置处理器的执行流程总结如下:
首先,要明白一点,上图分为左右两个部分,代表的不是两个接口,而是两个方法
- 一个是BeanDefinitionRegistryPostProcessor特有的postProcessBeanDefinitionRegistry方法
- 另一个是BeanFactoryPostProcessor的postProcessBeanFactory方法
这里我们以方法为维度区分更好说明问题,postProcessBeanDefinitionRegistry方法的执行时机早于postProcessBeanFactory。并且他们按照上图从左至右的顺序执行。
另外在上面进行代码分析的时候有一个问题,当在执行postProcessBeanDefinitionRegistry方法时,Spring采用了循环的方式,不断的查找是否有新增的BeanDefinitionRegistryPostProcessor,就是下面这段代码:
boolean reiterate = true;while (reiterate) {reiterate = false;postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);reiterate = true;}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);currentRegistryProcessors.clear();}但是在执行postProcessBeanFactory并没有进行类似的查找,这是为什么呢?
我自认为主要是Spring在设计时postProcessBeanFactory这个方法不是用于重新注册一个Bean的,而是修改,我们可以看下这个方法上的争端Java doc
其中最重要的一段话:All bean definitions will have been loaded,所有的BeanDefinition都已经被加载了。
再对比下postProcessBeanDefinitionRegistry这个方法上的Java doc
注意这段话:This allows for adding further bean definitions before the next post-processing phase kicks in.运行我们在下一个后置处理器执行前添加更多的BeanDefinition
使用过程的几个问题:
从技术上来说是可以的,但是正常情况下我们不该这么做,可能会存在该执行的bean工厂后置处理器没有被应用到这个bean上
不能,即使配置了也不会生效,我们将bean工厂后置处理器配置为懒加载这个行为本身就没任何意义
总结:
在这篇文章中,我们最需要了解及掌握的就是BeanFactoryPostProcessor执行的顺序,总结:
- 先执行直接实现了BeanDefinitionRegistryPostProcessor接口的后置处理器,所有实现了BeanDefinitionRegistryPostProcessor接口的类有两个方法,一个是特有的postProcessBeanDefinitionRegistry方法,一个是继承自父接口的postProcessBeanFactory
postProcessBeanDefinitionRegistry方法早于postProcessBeanFactory
方法执行,对于postProcessBeanDefinitionRegistry的执行顺序又遵循如下原子:
a. 先执行实现了PriorityOrdered接口类中的postProcessBeanDefinitionRegistry方法
b. 再执行实现了Ordered接口类中的postProcessBeanDefinitionRegistry方法
c. 最后执行没有实现上面两个接口类中的postProcessBeanDefinitionRegistry方法
执行完所有的postProcessBeanDefinitionRegistry方法后,再次执行实现了
BeanDefinitionRegistryPostProcessor接口类中的postProcessBeanDefinitionRegistry方法
- 再执行直接实现了BeanFactoryPostProcessor接口的后置处理器
a. 先执行实现了PriorityOrdered接口类中的postProcessBeanFactory方法
b. 再执行实现了Ordered接口类中的postProcessBeanFactory方法
c. 最后执行没有实现上面两个接口类中的postProcessBeanFactory方法
不吃竹子的滚滚:Spring源码分析(十四)Spring中的BeanWrapper及类型转换
不吃竹子的滚滚:Spring源码分析(十二)ApplicationContext详解(中)
不吃竹子的滚滚:Spring源码分析(十一)ApplicationContext详细介绍(上)
不吃竹子的滚滚:Spring源码分析(十)Spring中Bean的生命周期(下)
不吃竹子的滚滚:Spring源码分析(九)Spring中Bean的生命周期(上)
不吃竹子的滚滚:Spring源码分析(八)容器的扩展点(BeanPostProcessor)
不吃竹子的滚滚:Spring源码分析(七)容器的扩展点(FactoryBean)
不吃竹子的滚滚:Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)
不吃竹子的滚滚:Spring源码分析(五)BeanDefinition(下)
不吃竹子的滚滚:Spring源码分析(四)BeanDefinition(上)
不吃竹子的滚滚:Spring源码分析(三)自动注入与精确注入
不吃竹子的滚滚:Spring源码分析(二)依赖注入及方法注入
不吃竹子的滚滚:Spring源码分析(一)Spring容器及Spring Bean
总结
以上是生活随笔为你收集整理的beanfactorypostprocessor_Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 网络传输为什么要序列化_企业为什么要选择
- 下一篇: aop对请求后端的参数修改_Spring