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)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网络传输为什么要序列化_企业为什么要选择
- 下一篇: python画切片图_python|Py