上篇文章已經對
BeanDefinition
做了一系列的介紹,這篇文章我們開始學習BeanDefinition
合併的一些知識,完善我們整個BeanDefinition
的體系,Spring在創建一個bean時多次進行了BeanDefinition
的合併,對這方面有所瞭解也是爲以後閱讀源碼做準備。本文主要對應官網中的1.7
小節
在上篇文章中,我們學習了
BeanDefinition
的一些屬性,其中有以下幾個屬性:
// 是否抽象
boolean isAbstract();
// 獲取父BeanDefinition的名稱
String getParentName();
上篇文章中說過,這幾個屬性跟BeanDefinition
的合併相關,那麼我先考慮一個問題,什麼是合併呢?
什麼是合併?
我們來看官網上的一段介紹:
大概翻譯如下:
一個BeanDefinition
包含了很多的配置信息,包括構造參數,setter方法的參數還有容器特定的一些配置信息,比如初始化方法,靜態工廠方法等等。一個子的BeanDefinition
可以從它的父BeanDefinition
繼承配置信息,不僅如此,還可以覆蓋其中的一些值或者添加一些自己需要的屬性。使用BeanDefinition
的父子定義可以減少很多的重複屬性的設置,父BeanDefinition
可以作爲BeanDefinition
定義的模板。
我們通過一個例子來觀察下合併發生了什麼,編寫一個Demo如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="parent" abstract="true"
class="com.dmz.official.merge.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="child"
class="com.dmz.official.merge.DerivedTestBean"
parent="parent" >
<property name="name" value="override"/>
</bean>
</beans>
public class DerivedTestBean {
private String name;
private int age;
// 省略getter setter方法
}
public class TestBean {
private String name;
private String age;
// 省略getter setter方法
}
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("application.xml");
DerivedTestBean derivedTestBean = (DerivedTestBean) cc.getBean("child");
System.out.println("derivedTestBean的name = " + derivedTestBean.getName());
System.out.println("derivedTestBean的age = " + derivedTestBean.getAge());
}
}
運行:
derivedTestBean的name = override
derivedTestBean的age = 1
在上面的例子中,我們將DerivedTestBean
的parent
屬性設置爲了parent
,指向了我們的TestBean
,同時將TestBean
的age屬性設置爲1,但是我們在配置文件中並沒有直接設置DerivedTestBean
的age屬性。但是在最後運行結果,我們可以發現,DerivedTestBean
中的age屬性已經有了值,並且爲1,就是我們在其parent Bean(也就是TestBean
)中設置的值。也就是說,子BeanDefinition
會從父BeanDefinition
中繼承沒有的屬性。另外,DerivedTestBean
跟TestBean
都指定了name屬性,但是可以發現,這個值並沒有被覆蓋掉,也就是說,子BeanDefinition
中已經存在的屬性不會被父BeanDefinition
中所覆蓋。
合併的總結:
所以我們可以總結如下:
- 子
BeanDefinition
會從父BeanDefinition
中繼承沒有的屬性 - 這個過程中,子
BeanDefinition
中已經存在的屬性不會被父BeanDefinition
中所覆蓋
關於合併需要注意的點:
另外我們需要注意的是:
- 子
BeanDefinition
中的class
屬性如果爲null,同時父BeanDefinition
又指定了class
屬性,那麼子BeanDefinition
也會繼承這個class
屬性。 - 子
BeanDefinition
必須要兼容父BeanDefinition
中的所有屬性。這是什麼意思呢?以我們上面的demo爲例,我們在父BeanDefinition
中指定了name跟age屬性,但是如果子BeanDefinition
中子提供了一個name的setter方法,這個時候Spring在啓動的時候會報錯。因爲子BeanDefinition
不能承接所有來自父BeanDefinition
的屬性 - 關於
BeanDefinition
中abstract
屬性的說明:- 並不是作爲父
BeanDefinition
就一定要設置abstract
屬性爲true,abstract
只代表了這個BeanDefinition
是否要被Spring進行實例化並被創建對應的Bean,如果爲true,代表容器不需要去對其進行實例化。 - 如果一個
BeanDefinition
被當作父BeanDefinition
使用,並且沒有指定其class
屬性。那麼必須要設置其abstract
爲true abstract=true
一般會跟父BeanDefinition
一起使用,因爲當我們設置某個BeanDefinition
的abstract=true
時,一般都是要將其當作BeanDefinition
的模板使用,否則這個BeanDefinition
也沒有意義,除非我們使用其它BeanDefinition
來繼承它的屬性
- 並不是作爲父
Spring在哪些階段做了合併?
下文將所有
BeanDefinition
簡稱爲bd
1、掃描並獲取到bd
:
這個階段的操作主要發生在invokeBeanFactoryPostProcessors
,對應方法的調用棧如下:
對應的執行該方法的類爲:PostProcessorRegistrationDelegate
方法源碼如下:
public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory,
List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
// .....
// 省略部分代碼,省略的代碼主要時用來執行程序員手動調用API註冊的容器的後置處理器
// .....
// 發生一次bd的合併
// 這裏只會獲取實現了BeanDefinitionRegistryPostProcessor接口的Bean的名字
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
// 篩選實現了PriorityOrdered接口的後置處理器
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
// 去重
processedBeans.add(ppName);
}
}
// .....
// 只存在一個internalConfigurationAnnotationProcessor 處理器,用於掃描
// 這裏只會執行了實現了PriorityOrdered跟BeanDefinitionRegistryPostProcessor的後置處理器
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
// .....
// 這裏又進行了一個bd的合併
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
// 篩選實現了Ordered接口的後置處理器
if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
// .....
// 執行的是實現了BeanDefinitionRegistryPostProcessor接口跟Ordered接口的後置處理器
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
boolean reiterate = true;
while (reiterate) {
reiterate = false;
// 這裏再次進行了一次bd的合併
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (!processedBeans.contains(ppName)) {
// 篩選只實現了BeanDefinitionRegistryPostProcessor的後置處理器
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
reiterate = true;
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
// 執行的是普通的後置處理器,即沒有實現任何排序接口(PriorityOrdered或Ordered)
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
}
// .....
// 省略部分代碼,這部分代碼跟BeanfactoryPostProcessor接口相關,這節bd的合併無關,下節容器的擴展點中我會介紹
// .....
}
大家可以結合我畫的圖跟上面的代碼過一遍流程,只要弄清楚一點就行,即每次調用beanFactory.getBeanNamesForType
都進行了一次bd
的合併。getBeanNamesForType
這個方法主要目的是爲了或者指定類型的bd
的名稱,之後通過bd
的名稱去找到指定的bd
,然後獲取對應的Bean,比如上面方法三次獲取的都是BeanDefinitionRegistryPostProcessor
這個類型的bd
。
我們可以思考一個問題,爲什麼這一步需要合併呢?大家可以帶着這個問題繼續往下看,在後文我會解釋。
2、實例化
Spring在實例化一個對象也會進行bd
的合併。
第一次:
org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
public void preInstantiateSingletons() throws BeansException {
// .....
// 省略跟合併無關的代碼
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// .....
第二次:
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// .....
// 省略跟合併無關的代碼
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
// ....
}
if (mbd.isSingleton()) {
// ....
}
// ....
我們可以發現這兩次合併有一個共同的特點,就是在合併之後立馬利用了合併之後的bd
我們簡稱爲mbd
做了一系列的判斷,比如上面的dependsOn != null
和mbd.isSingleton()
。基於上面幾個例子我們來分析:爲什麼需要合併?
爲什麼需要合併?
在掃描階段,之所以發生了合併,是因爲Spring需要拿到指定了實現了BeanDefinitionRegistryPostProcessor
接口的bd
的名稱,也就是說,Spring需要用到bd
的名稱。所以進行了一次bd
的合併。在實例化階段,是因爲Spring需要用到bd
中的一系列屬性做判斷所以進行了一次合併。我們總結起來,其實就是一個原因:Spring需要用到bd
的屬性,要保證獲取到的bd
的屬性是正確的。
那麼問題來了,爲什麼獲取到的bd
中屬性可能不正確呢?
主要兩個原因:
- 作爲子
bd
,屬性本身就有可能缺失,比如我們在開頭介紹的例子,子bd
中本身就沒有age屬性,age屬性在父bd
中 - Spring提供了很多擴展點,在啓動容器的時候,可能會修改
bd
中的屬性。比如一個正常實現了BeanFactoryPostProcessor
就能修改容器中的任意的bd
的屬性。在後面的容器的擴展點中我再介紹
合併的代碼分析:
因爲合併的代碼其實很簡單,所以一併在這裏分析了,也可以加深對合並的理解:
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
// Quick check on the concurrent map first, with minimal locking.
// 從緩存中獲取合併後的bd
RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
if (mbd != null) {
return mbd;
}
// 如何獲取不到的話,開始真正的合併
return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
}
protected RootBeanDefinition getMergedBeanDefinition(
String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
throws BeanDefinitionStoreException {
synchronized (this.mergedBeanDefinitions) {
RootBeanDefinition mbd = null;
// Check with full lock now in order to enforce the same merged instance.
if (containingBd == null) {
mbd = this.mergedBeanDefinitions.get(beanName);
}
if (mbd == null) {
// 如果沒有parentName的話直接使用自身合併
// 就是new了RootBeanDefinition然後再進行屬性的拷貝
if (bd.getParentName() == null) {
if (bd instanceof RootBeanDefinition) {
mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
}
else {
mbd = new RootBeanDefinition(bd);
}
}
else {
// 需要進行父子的合併
BeanDefinition pbd;
try {
String parentBeanName = transformedBeanName(bd.getParentName());
if (!beanName.equals(parentBeanName)) {
// 這裏是遞歸,在將父子合併時,需要確保父bd已經合併過了
pbd = getMergedBeanDefinition(parentBeanName);
}
else {
// 一般不會進這個判斷
// 到父容器中找對應的bean,然後進行合併,合併也發生在父容器中
BeanFactory parent = getParentBeanFactory();
if (parent instanceof ConfigurableBeanFactory) {
pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
}
// 省略異常信息......
}
}
// 省略異常信息......
//
mbd = new RootBeanDefinition(pbd);
//用子bd中的屬性覆蓋父bd中的屬性
mbd.overrideFrom(bd);
}
// 默認設置爲單例
if (!StringUtils.hasLength(mbd.getScope())) {
mbd.setScope(RootBeanDefinition.SCOPE_SINGLETON);
}
// 當前bd如果內部嵌套了一個bd,並且嵌套的bd不是單例的,但是當前的bd又是單例的
// 那麼將當前的bd的scope設置爲嵌套bd的類型
if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
mbd.setScope(containingBd.getScope());
}
// 將合併後的bd放入到mergedBeanDefinitions這個map中
// 之後還是可能被清空的,因爲bd可能被修改
if (containingBd == null && isCacheBeanMetadata()) {
this.mergedBeanDefinitions.put(beanName, mbd);
}
}
return mbd;
}
}
上面這段代碼整體不難理解,可能發生疑惑的主要是兩個點:
pbd = getMergedBeanDefinition(parentBeanName);
這裏進行的是父bd
的合併,是方法的遞歸調用,這是因爲在合併的時候父bd
可能也還不是一個合併後的bd
containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()
我查了很久的資料,經過驗證後發現,如果進行了形如下面的嵌套配置,那麼containingBd
會不爲null
<bean id="luBanService" class="com.dmz.official.service.LuBanService" scope="prototype">
<property name="lookUpService">
<bean class="com.dmz.official.service.LookUpService" scope="singleton"></bean>
</property>
</bean>
在這個例子中,containingBd
爲LuBanService
,此時,LuBanService
是一個原型的bd
,但lookUpService
是一個單例的bd
,那麼這個時候經過合併,LookUpService
也會變成一個原型的bd
。大家可以拿我這個例子測試一下。
總結:
這篇文章我覺得最重要的是,我們要明白Spring爲什麼要進行合併,之所以再每次需要用到BeanDefinition
都進行一次合併,是爲了每次都拿到最新的,最有效的BeanDefinition
,因爲利用容器提供了一些擴展點我們可以修改BeanDefinition
中的屬性。關於容器的擴展點,比如上文提到了BeanFactoryPostProcessor
以及BeanDefinitionRegistryPostProcessor
,我會在後面的幾篇文章中一一介紹。
BeanDefinition
的學習就到這裏了,這個類很重要,是整個Spring的基石,希望大家可以多花時間多研究研究相關的知識。加油,共勉!
掃描下方二維碼,關注我的公衆號,更多精彩文章在等您!~~