Spring中常見的7種BeanDefinition詳解
今天我們繼續(xù)來看 Spring 源碼中一個非常重要的概念:BeanDefinition。
1.BeanDefinition
在 Spring 容器中,我們廣泛使用的是一個一個的 Bean,BeanDefinition 從名字上就可以看出是關(guān)于 Bean 的定義。
事實上就是這樣,我們在 XML 文件中配置的 Bean 的各種屬性,亦或者用注解定義出來的 Bean 的各種屬性,在真正生成 Bean 直接,我們需要先對這些設(shè)置的屬性進行解析,解析的結(jié)果需要有一個對象來承載,很明顯,這個對象就是 BeanDefinition。
無論是通過 XML 中定義的 Bean 屬性還是通過 Java 代碼定義的 Bean 屬性,都會先加載到 BeanDefinition 上,然后通過 BeanDefinition 來生成一個 Bean,從這個角度來說,BeanDefinition 和 Bean 的關(guān)系有點類似于類和對象的關(guān)系,BeanDefinition 是模板,Bean 是模板具體化之后的產(chǎn)物。
要理解 BeanDefinition,我們從 BeanDefinition 的繼承關(guān)系開始看起。

BeanDefinition 是一個接口,繼承自 BeanMetadataElement 和 AttributeAccessor 接口。
- BeanMetadataElement:該接口只有一個方法 getSource,該方法返回 Bean 的來源。
- AttributeAccessor:該接口主要規(guī)范了問任意對象元數(shù)據(jù)的方法。
我們來看下 AttributeAccessor:
public?interface?AttributeAccessor?{
?void?setAttribute(String?name,?@Nullable?Object?value);
?@Nullable
?Object?getAttribute(String?name);
?@Nullable
?Object?removeAttribute(String?name);
?boolean?hasAttribute(String?name);
?String[]?attributeNames();
}這里定義了元數(shù)據(jù)的訪問接口,具體的實現(xiàn)則是 AttributeAccessorSupport,這些數(shù)據(jù)采用 LinkedHashMap 進行存儲。
這是 BeanDefinition 所繼承的兩個接口。接下來我們來看下 BeanDefinition 接口:
public?interface?BeanDefinition?extends?AttributeAccessor,?BeanMetadataElement?{
?String?SCOPE_SINGLETON?=?ConfigurableBeanFactory.SCOPE_SINGLETON;
?String?SCOPE_PROTOTYPE?=?ConfigurableBeanFactory.SCOPE_PROTOTYPE;
?int?ROLE_APPLICATION?=?0;
?int?ROLE_SUPPORT?=?1;
?int?ROLE_INFRASTRUCTURE?=?2;
?void?setParentName(@Nullable?String?parentName);
?@Nullable
?String?getParentName();
?void?setBeanClassName(@Nullable?String?beanClassName);
?@Nullable
?String?getBeanClassName();
?void?setScope(@Nullable?String?scope);
?@Nullable
?String?getScope();
?void?setLazyInit(boolean?lazyInit);
?boolean?isLazyInit();
?void?setDependsOn(@Nullable?String...?dependsOn);
?@Nullable
?String[]?getDependsOn();
?void?setAutowireCandidate(boolean?autowireCandidate);
?boolean?isAutowireCandidate();
?void?setPrimary(boolean?primary);
?boolean?isPrimary();
?void?setFactoryBeanName(@Nullable?String?factoryBeanName);
?@Nullable
?String?getFactoryBeanName();
?void?setFactoryMethodName(@Nullable?String?factoryMethodName);
?@Nullable
?String?getFactoryMethodName();
?ConstructorArgumentValues?getConstructorArgumentValues();
?default?boolean?hasConstructorArgumentValues()?{
??return?!getConstructorArgumentValues().isEmpty();
?}
?MutablePropertyValues?getPropertyValues();
?default?boolean?hasPropertyValues()?{
??return?!getPropertyValues().isEmpty();
?}
?void?setInitMethodName(@Nullable?String?initMethodName);
?@Nullable
?String?getInitMethodName();
?void?setDestroyMethodName(@Nullable?String?destroyMethodName);
?@Nullable
?String?getDestroyMethodName();
?void?setRole(int?role);
?int?getRole();
?void?setDescription(@Nullable?String?description);
?@Nullable
?String?getDescription();
?ResolvableType?getResolvableType();
?boolean?isSingleton();
?boolean?isPrototype();
?boolean?isAbstract();
?@Nullable
?String?getResourceDescription();
?@Nullable
?BeanDefinition?getOriginatingBeanDefinition();
}BeanDefinition 中的方法雖然多,但是結(jié)合我們平時在 XML/Java 中的配置,這些方法其實都很好理解:
- 首先一開始定義了兩個變量用來描述 Bean 是不是單例的,后面的 setScope/getScope 方法可以用來修改/獲取 scope 屬性。
- ROLE_xxx 用來描述一個 Bean 的角色,ROLE_APPLICATION 表示這個 Bean 是用戶自己定義的 Bean;ROLE_SUPPORT 表示這個 Bean 是某些復(fù)雜配置的支撐部分;ROLE_INFRASTRUCTURE 表示這是一個 Spring 內(nèi)部的 Bean,通過 setRole/getRole 可以修改。
- setParentName/getParentName 用來配置 parent 的名稱,這塊可能有的小伙伴使用較少,這個對應(yīng)著 XML 中的
<bean parent="">配置,在之前的視頻中松哥已經(jīng)和大家講過了 Spring 中 parent 的使用了。 - setBeanClassName/getBeanClassName 這個就是配置 Bean 的 Class 全路徑,對應(yīng) XML 中的
<bean class="">配置。 - setLazyInit/isLazyInit 配置/獲取 Bean 是否懶加載,這個對應(yīng)了 XML 中的
<bean lazy-init="">配置。 - setDependsOn/getDependsOn 配置/獲取 Bean 的依賴對象,這個對應(yīng)了 XML 中的
<bean depends-on="">配置。 - setAutowireCandidate/isAutowireCandidate 配置/獲取 Bean 是否是自動裝配,對應(yīng)了 XML 中的
<bean autowire-candidate="">配置。 - setPrimary/isPrimary 配置/獲取當(dāng)前 Bean 是否為首選的 Bean,對應(yīng)了 XML 中的
<bean primary="">配置。 - setFactoryBeanName/getFactoryBeanName 配置/獲取 FactoryBean 的名字,對應(yīng)了 XML 中的
<bean factory-bean="">配置。 - setFactoryMethodName/getFactoryMethodName 和上一條成對出現(xiàn)的,對應(yīng)了 XML 中的
<bean factory-method="">配置,不再贅述。 - getConstructorArgumentValues 返回該 Bean 構(gòu)造方法的參數(shù)值。
- hasConstructorArgumentValues 判斷上一條是否是空對象。
- getPropertyValues 這個是獲取普通屬性的集合。
- hasPropertyValues 判斷上一條是否為空對象。
- setInitMethodName/setDestroyMethodName 配置 Bean 的初始化方法、銷毀方法。
- setDescription/getDescription 配置/返回 Bean 的描述。
- isSingleton Bean 是否為單例。
- isPrototype Bean 是否為原型。
- isAbstract Bean 是否抽象。
- getResourceDescription 返回定義 Bean 的資源描述。
- getOriginatingBeanDefinition 如果當(dāng)前 BeanDefinition 是一個代理對象,那么該方法可以用來返回原始的 BeanDefinition 。
這個就是 BeanDefinition 的定義以及它里邊方法的含義。
2.BeanDefinition 實現(xiàn)類
上面只是 BeanDefinition 接口的定義,BeanDefinition 還擁有諸多實現(xiàn)類,我們也來大致了解下。
先來看一張繼承關(guān)系圖:

這么多實現(xiàn)類看著有點眼花繚亂,不過搞清楚了每一個接口和類的作用,再看就很容易了。
2.1 AbstractBeanDefinition
AbstractBeanDefinition 是一個抽象類,它根據(jù) BeanDefinition 中定義的接口提供了相應(yīng)的屬性,并實現(xiàn)了 BeanDefinition 中定義的一部分方法。BeanDefinition 中原本只是定義了一系列的 get/set 方法,并沒有提供對應(yīng)的屬性,在 AbstractBeanDefinition 中將所有的屬性定義出來了。
后面其他的實現(xiàn)類也基本上都是在 AbstractBeanDefinition 的基礎(chǔ)上完成的。
2.2 RootBeanDefinition
這是一個比較常用的實現(xiàn)類,對應(yīng)了一般的元素標(biāo)簽。
2.3 ChildBeanDefinition
可以讓子 BeanDefinition 定義擁有從父 BeanDefinition 那里繼承配置的能力,如果子 Bean 從父 Bean 獲取配置,可以參考松哥之前的這篇文章:Spring BeanDefinition 也分父子?。
2.4 GenericBeanDefinition
GenericBeanDefinition 是從 Spring2.5 以后新加入的 BeanDefinition 實現(xiàn)類。GenericBeanDefinition 可以動態(tài)設(shè)置父 Bean,同時兼具 RootBeanDefinition 和 ChildBeanDefinition 的功能,因此,自從有了 GenericBeanDefinition 之后,RootBeanDefinition 和 ChildBeanDefinition 現(xiàn)在相對就用的少了。
2.5 AnnotatedBeanDefinition
這個表示注解類型 BeanDefinition,用于表示通過注解配置的 Bean 定義。通過 AnnotatedBeanDefinition,我們可以獲取到被注解的 Bean 的相關(guān)信息,包括注解類型、屬性值、方法等。這個接口提供了一種方便的方式來處理通過注解方式配置的 Bean,并且可以在運行時動態(tài)地獲取和操作這些注解信息,當(dāng)然,這是一個接口,它有三個實現(xiàn)類,分別是 AnnotatedGenericBeanDefinition、ScannedGenericBeanDefinition 以及 ConfigurationClassBeanDefinition。
2.6 AnnotatedGenericBeanDefinition
使用了 @Configuration 注解標(biāo)記配置類會解析為 AnnotatedGenericBeanDefinition。
2.7 ScannedGenericBeanDefinition
這個是通過包掃描自動注冊的 Bean,就會被解析為 ScannedGenericBeanDefinition。
2.8 ConfigurationClassBeanDefinition
這是一個私有的內(nèi)部類。我們通過 @Bean 注解定義的 Bean,最終會被解析為 ConfigurationClassBeanDefinition。
2.9 ClassDerivedBeanDefinition
ClassDerivedBeanDefinition 的作用是擴展并描述一個類派生的Bean的元數(shù)據(jù)。它是 AnnotatedBeanDefinition 接口的一個實現(xiàn)類,在 Spring 框架中用于表示通過類派生方式配置的Bean定義。
2.10 CreateFromClassBeanDefinition
這個是按照類型創(chuàng)建 Bean 的時候會用到。
差不多就這么多了,大部分我們?nèi)粘i_發(fā)中其實都用不上,接下來松哥通過幾個具體的案例來和小伙伴們演示這些 BeanDefinition 的具體用法。后面的文章,我們再來分析這些 BeanDefinition 在 Spring 源碼中是如何應(yīng)用的。
3.實踐
接下來我通過幾個具體的案例來和小伙伴們演示各種不同的 BeanDefinition 的用法,今天我主要和小伙伴們演示我們純手動使用 BeanDefinition,然后分析一下我們平時的配置本質(zhì)上使用的 BeanDefinition 是哪一個,今天我們先不去源碼分析,單純的就看看效果。
3.1 GenericBeanDefinition
先來看 GenericBeanDefinition,這個功能相對比較全,兼具 RootBeanDefinition 和 ChildBeanDefinition 的能力。
先來看一個簡單用法:
DefaultListableBeanFactory?beanFactory?=?new?DefaultListableBeanFactory();
GenericBeanDefinition?bd?=?new?GenericBeanDefinition();
bd.setBeanClass(User.class);
MutablePropertyValues?pValues?=?new?MutablePropertyValues();
pValues.add("username",?"javaboy");
bd.setPropertyValues(pValues);
beanFactory.registerBeanDefinition("user",?bd);
User?user?=?beanFactory.getBean("user",?User.class);
System.out.println("user?=?"?+?user);小伙伴們看到,我們這里向 Spring 容器注冊了一個 GenericBeanDefinition 類型的 BeanDefinition,GenericBeanDefinition 中包含了具體的 class 以及 Bean 的各個屬性值。
如果我們在 Bean 定義的時候,想要使用繼承特性,也可以使用 GenericBeanDefinition:
DefaultListableBeanFactory?beanFactory?=?new?DefaultListableBeanFactory();
GenericBeanDefinition?parentBD?=?new?GenericBeanDefinition();
GenericBeanDefinition?childBD?=?new?GenericBeanDefinition();
parentBD.setBeanClass(Animal.class);
MutablePropertyValues?pValues?=?new?MutablePropertyValues();
pValues.add("name",?"小黃");
parentBD.setPropertyValues(pValues);
childBD.setBeanClass(Dog.class);
childBD.setParentName("animal");
beanFactory.registerBeanDefinition("animal",?parentBD);
beanFactory.registerBeanDefinition("dog",?childBD);
Dog?dog?=?beanFactory.getBean("dog",?Dog.class);
System.out.println("dog?=?"?+?dog);這次我直接定義了兩個 GenericBeanDefinition,一個作為 parent,另外一個作為 child,為 child 設(shè)置 parentName,則 child 可以繼承 parent 中的屬性。上面的案例中,最終打印出來 dog 的 name 屬性就是 小黃,這塊小伙伴們可以參考松哥之前的文章:Spring BeanDefinition 也分父子?。
我們平時通過 XML 文件定義的 Bean,最終解析后就是 GenericBeanDefinition。
例如下面這樣:
<?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?class="org.javaboy.demo.User"?id="user"></bean> </beans>
加載 XML 文件,創(chuàng)建容器:
ClassPathXmlApplicationContext?ctx?=?new?ClassPathXmlApplicationContext("beans.xml");
String[]?beanDefinitionNames?=?ctx.getBeanDefinitionNames();
for?(String?beanDefinitionName?:?beanDefinitionNames)?{
????BeanDefinition?bd?=?ctx.getBeanFactory().getBeanDefinition(beanDefinitionName);
????System.out.println(beanDefinitionName?+?">>>"?+?bd.getClass());
}最終打印結(jié)果如下:

這個也好理解,畢竟我們在 XML 中配置的時候,可能存在 parent,也可能不存在,用 GenericBeanDefinition 就能夠應(yīng)對各種情況。
3.2 RootBeanDefinition/ChildBeanDefinition
這兩個常規(guī)的功能其實都有,但是 RootBeanDefinition 一般可以用來做 parent,不能用作 child,即給 RootBeanDefinition 不能配置 parentName 屬性。強行設(shè)置會拋出如下異常:
@Override
public?void?setParentName(@Nullable?String?parentName)?{
?if?(parentName?!=?null)?{
??throw?new?IllegalArgumentException("Root?bean?cannot?be?changed?into?a?child?bean?with?parent?reference");
?}
}ChildBeanDefinition 則既可以做 parent 也可以做 child,但是 ChildBeanDefinition 在使用的使用必須指定 parent,即使 ChildBeanDefinition 作為 parent,也必須指定 parent,所以 ChildBeanDefinition 在使用的過程中有一點點局限性,因此目前被 GenericBeanDefinition 代替了。
來看一個簡單的案例:
DefaultListableBeanFactory?beanFactory?=?new?DefaultListableBeanFactory();
RootBeanDefinition?parentBD?=?new?RootBeanDefinition();
parentBD.setBeanClass(Animal.class);
MutablePropertyValues?pValues?=?new?MutablePropertyValues();
pValues.add("name",?"小黃");
parentBD.setPropertyValues(pValues);
ChildBeanDefinition?childBD?=?new?ChildBeanDefinition("animal");
childBD.setBeanClass(Dog.class);
beanFactory.registerBeanDefinition("animal",?parentBD);
beanFactory.registerBeanDefinition("dog",?childBD);
Dog?dog?=?beanFactory.getBean("dog",?Dog.class);
System.out.println("dog?=?"?+?dog);3.3 AnnotatedGenericBeanDefinition
對于使用 @Configuration 注解標(biāo)記的類,最終解析出來的 BeanDefinition 就是 AnnotatedGenericBeanDefinition。例如我有一個配置類如下:
@Configuration
public?class?JavaConfig?{
}加載配置類并啟動容器:
AnnotationConfigApplicationContext?ctx?=?new?AnnotationConfigApplicationContext(JavaConfig.class);
String[]?beanDefinitionNames?=?ctx.getBeanDefinitionNames();
for?(String?beanDefinitionName?:?beanDefinitionNames)?{
????BeanDefinition?beanDefinition?=?ctx.getBeanFactory().getBeanDefinition(beanDefinitionName);
????System.out.println(beanDefinitionName?+?"?>>>?"?+?beanDefinition.getClass());
}最終打印結(jié)果如下:

3.4 ScannedGenericBeanDefinition
這個是那些通過包掃描注冊到 Spring 容器中的 Bean,在一開始定義出來的 BeanDefinition 就是 ScannedGenericBeanDefinition。
例如我有如下 Bean:
@Service
public?class?UserService?{
}然后配置包掃描:
@Configuration
@ComponentScan
public?class?JavaConfig?{
}啟動容器:
AnnotationConfigApplicationContext?ctx?=?new?AnnotationConfigApplicationContext(JavaConfig.class);
String[]?beanDefinitionNames?=?ctx.getBeanDefinitionNames();
for?(String?beanDefinitionName?:?beanDefinitionNames)?{
????BeanDefinition?beanDefinition?=?ctx.getBeanFactory().getBeanDefinition(beanDefinitionName);
????System.out.println(beanDefinitionName?+?"?>>>?"?+?beanDefinition.getClass());
}最終打印結(jié)果如下:

3.5 ConfigurationClassBeanDefinition
當(dāng)我們通過 @Bean 注解去定義 Bean 的時候,那么被 @Bean 注解標(biāo)記的類就會被解析為 ConfigurationClassBeanDefinition。
例如下面這個例子:
@Configuration
public?class?JavaConfig?{
????@Bean
????User?user()?{
????????return?new?User();
????}
}查看解析后的 BeanDefinition 如下:

3.6 CreateFromClassBeanDefinition
這個其實用的少,但是咱么既然講到 Spring,松哥也說兩句。
這個是當(dāng)我們想要創(chuàng)建一個對象,我們希望這個對象能夠自動走一遍 Spring 中的各種后置處理器的時候,那么可以調(diào)用 createBean 方法,該方法內(nèi)部使用了 CreateFromClassBeanDefinition。
例如如下代碼:
AnnotationConfigApplicationContext?ctx?=?new?AnnotationConfigApplicationContext(JavaConfig.class);
ConfigurableListableBeanFactory?beanFactory?=?ctx.getBeanFactory();
User?user?=?beanFactory.createBean(User.class);
System.out.println("user?=?"?+?user);使用這種方式去創(chuàng)建一個 Bean,這個 Bean 會走一遍 Spring 中 Bean 的后置處理器,其中,createBean 方法的內(nèi)部就使用了 CreateFromClassBeanDefinition。
3.7 ClassDerivedBeanDefinition
ClassDerivedBeanDefinition 和 CreateFromClassBeanDefinition 其實比較像,差別在于二者處理構(gòu)造方法的方式不同。
而且 ClassDerivedBeanDefinition 是一個相當(dāng)冷門的 BeanDefinition,在 GenericApplicationContext 的實現(xiàn)類中,可以使用 GenericXmlApplicationContext、StaticApplicationContext 或者 GenericGroovyApplicationContext,只有這三個類中 registerBean 方法用到了 ClassDerivedBeanDefinition,我們常見的 AnnotationConfigApplicationContext 由于方法重寫的緣故并未使用 ClassDerivedBeanDefinition。
StaticApplicationContext?ctx?=?new?StaticApplicationContext();
ctx.registerBean(User.class,()->{
????User?user?=?new?User();
????user.setUsername("javaboy");
????return?user;
});
User?user?=?ctx.getBean(User.class);
System.out.println("user?=?"?+?user);
String[]?beanDefinitionNames?=?ctx.getBeanDefinitionNames();
for?(String?beanDefinitionName?:?beanDefinitionNames)?{
????BeanDefinition?bd?=?ctx.getBeanDefinition(beanDefinitionName);
????System.out.println(beanDefinitionName?+?">>>"?+?bd.getClass());
}我們可以調(diào)用 registerBean 方法向 Spring 容器中注入一個 Bean,該方法第二個參數(shù)是一個 Bean 的生產(chǎn)者,如果不指定生產(chǎn)者,那么這個方法最終就是通過第一個參數(shù)反射創(chuàng)建 Bean,registerBean 方法的內(nèi)部就是使用了 ClassDerivedBeanDefinition。
好啦,BeanDefinition 一共就是這七種,接下來我會通過幾篇文章和大家重點介紹 GenericBeanDefinition、AnnotatedGenericBeanDefinition、ScannedGenericBeanDefinition 以及 ConfigurationClassBeanDefinition 這四種最為常見的 BeanDefinition。
以上就是Spring中常見的7種BeanDefinition詳解的詳細內(nèi)容,更多關(guān)于Spring BeanDefinition的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之哈希算法實現(xiàn)
哈希表本質(zhì)是一種(key,value)結(jié)構(gòu),由此我們可以聯(lián)想到,能不能把哈希表的key映射成數(shù)組的索引index呢?如果這樣做的話那么查詢相當(dāng)于直接查詢索引,查詢時間復(fù)雜度為O(1),其實這也正是當(dāng)key為int型時的做法,將key通過某種做法映射成index,從而轉(zhuǎn)換成數(shù)組結(jié)構(gòu)2022-02-02
maven倉庫中心mirrors配置多個下載中心(執(zhí)行最快的鏡像)
這篇文章主要介紹了maven倉庫中心mirrors配置多個下載中心(執(zhí)行最快的鏡像),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
面試題:java中為什么foreach中不允許對元素進行add和remove
讀者遇到了一個比較經(jīng)典的面試題,也就是標(biāo)題上說的,為什么 foreach 中不允許對元素進行 add 和 remove,本文就詳細的介紹一下,感興趣的可以了解一下2021-10-10

