前面幾篇文章已經學習了官網中的
1.2
,1.3
,1.4
三小結,主要是容器,Bean的實例化及Bean之間的依賴關係等。這篇文章,我們繼續官網的學習,主要是BeanDefinition
的相關知識,這是Spring中非常基礎的一塊內容,也是我們閱讀源碼的基石。本文主要涉及到官網中的1.3
及1.5
中的一些補充知識。同時爲我們1.7
小節中BeanDefinition
的合併做一些鋪墊
文章目錄
BeanDefinition是什麼?
我們先看官網上是怎麼解釋的:
從上文中,我們可以得出以下幾點結論:
BeanDefinition
包含了我們對bean做的配置,比如XML<bean/>
標籤的形式進行的配置- 換而言之,Spring將我們對bean的定義信息進行了抽象,抽象後的實體就是
BeanDefinition
,並且Spring會以此作爲標準來對Bean進行創建 BeanDefinition
包含以下元數據:- 一個全限定類名,通常來說,就是對應的bean的全限定類名。
- bean的行爲配置元素,這些元素展示了這個bean在容器中是如何工作的包括
scope
(域,我們文末有簡單介紹),lifecycle callbacks
(生命週期回調,下篇文章介紹)等等 - 這個bean的依賴信息
- 一些其他配置信息,比如我們配置了一個連接池對象,那麼我們還會配置它的池子大小,最大連接數等等
在這裏,我們來比較下,正常的創建一個bean,跟Spring通過抽象出一個BeanDefinition
來創建bean有什麼區別:
正常的創建一個java bean:
Spring通過BeanDefinition
來創建bean:
通過上面的比較,我們可以發現,相比於正常的對象的創建過程,Spring對其管理的bean沒有直接採用new的方式,而是先通過解析配置數據以及根據對象本身的一些定義而獲取其對應的beandefinition
,並將這個beandefinition
作爲之後創建這個bean的依據。同時Spring在這個過程中提供了一些擴展點,例如我們在圖中所提到了BeanfactoryProcessor
。這些大家先作爲了解,之後在源碼階段我們再分析。
BeanDefinition的方法分析
這裏對於每個字段我只保留了一個方法,只要知道了字段的含義,方法的含義我們自然就知道了
// 獲取父BeanDefinition,主要用於合併,下節中會詳細分析
String getParentName();
// 對於的bean的ClassName
void setBeanClassName(@Nullable String beanClassName);
// Bean的作用域,不考慮web容器,主要兩種,單例/原型,見官網中1.5內容
void setScope(@Nullable String scope);
// 是否進行懶加載
void setLazyInit(boolean lazyInit);
// 是否需要等待指定的bean創建完之後再創建
void setDependsOn(@Nullable String... dependsOn);
// 是否作爲自動注入的候選對象
void setAutowireCandidate(boolean autowireCandidate);
// 是否作爲主選的bean
void setPrimary(boolean primary);
// 創建這個bean的類的名稱
void setFactoryBeanName(@Nullable String factoryBeanName);
// 創建這個bean的方法的名稱
void setFactoryMethodName(@Nullable String factoryMethodName);
// 構造函數的參數
ConstructorArgumentValues getConstructorArgumentValues();
// setter方法的參數
MutablePropertyValues getPropertyValues();
// 生命週期回調方法,在bean完成屬性注入後調用
void setInitMethodName(@Nullable String initMethodName);
// 生命週期回調方法,在bean被銷燬時調用
void setDestroyMethodName(@Nullable String destroyMethodName);
// Spring可以對bd設置不同的角色,瞭解即可,不重要
// 用戶定義 int ROLE_APPLICATION = 0;
// 某些複雜的配置 int ROLE_SUPPORT = 1;
// 完全內部使用 int ROLE_INFRASTRUCTURE = 2;
void setRole(int role);
// bean的描述,沒有什麼實際含義
void setDescription(@Nullable String description);
// 根據scope判斷是否是單例
boolean isSingleton();
// 根據scope判斷是否是原型
boolean isPrototype();
// 跟合併beanDefinition相關,如果是abstract,說明會被作爲一個父beanDefinition,不用提供class屬性
boolean isAbstract();
// bean的源描述,沒有什麼實際含義
String getResourceDescription();
// cglib代理前的BeanDefinition
BeanDefinition getOriginatingBeanDefinition();
BeanDefinition的繼承關係
類圖如下:
1.BeanDefinition繼承的接口
org.springframework.core.AttributeAccessor
先來看接口上標註的這段java doc
Interface defining a generic contract for attaching and accessing metadata to/from arbitrary objects.
翻譯下來就是:
這個接口爲從其它任意類中獲取或設置元數據提供了一個通用的規範。
其實這就是訪問者
模式的一種體現,採用這方方法,我們可以將數據接口跟操作方法進行分離。
我們再來看這個接口中定義的方法:
void setAttribute(String name, @Nullable Object value);
Object getAttribute(String name);
Object removeAttribute(String name);
boolean hasAttribute(String name);
String[] attributeNames();
就是提供了一個獲取屬性跟設置屬性的方法
那麼現在問題來了,在我們整個BeanDefiniton
體系中,這個被操作的數據結構在哪呢?不要急,在後文中的AbstractBeanDefinition
會介紹。
org.springframework.beans.BeanMetadataElement
我們還是先看java doc
:
Interface to be implemented by bean
metadata
elements that carry a configuration source object.
翻譯:這個接口提供了一個方法去獲取配置源對象,其實就是我們的原文件。
這個接口只提供了一個方法:
@Nullable
Object getSource();
我們可以理解爲,當我們通過註解的方式定義了一個IndexService
時,那麼此時的IndexService
對應的BeanDefinition
通過getSource
方法返回的就是IndexService.class
這個文件對應的一個File
對象。
如果我們通過@Bean
方式定義了一個IndexService
的話,那麼此時的source是被@Bean
註解所標註的一個Mehthod
對象。
2.AbstractBeanDefinition
AbstractBeanDefinition的繼承關係
先看一下類圖:
org.springframework.core.AttributeAccessorSupport
可以看到這個類實現了AttributeAccerror
接口,我們在上文中已經提到過,AttributeAccerror
採用了訪問者的涉及模式,將數據結構跟操作方法進行了分離,數據結構在哪呢?就在AttributeAccessorSupport
這個類中,我們看下它的代碼:
public abstract class AttributeAccessorSupport implements AttributeAccessor, Serializable {
/** Map with String keys and Object values. */
private final Map<String, Object> attributes = new LinkedHashMap<>();
@Override
public void setAttribute(String name, @Nullable Object value) {
Assert.notNull(name, "Name must not be null");
if (value != null) {
this.attributes.put(name, value);
}
else {
removeAttribute(name);
}
}
......省略下面的代碼
可以看到,在這個類中,維護了一個map,這就是BeanDefinition
體系中,通過訪問者模式
所有操作的數據對象。
org.springframework.beans.BeanMetadataAttributeAccessor
這個類主要就是對我們上面的map中的數據操作做了更深一層的封裝,我們就看其中的兩個方法:
public void addMetadataAttribute(BeanMetadataAttribute attribute) {
super.setAttribute(attribute.getName(), attribute);
}
public BeanMetadataAttribute getMetadataAttribute(String name) {
return (BeanMetadataAttribute) super.getAttribute(name);
}
可以發現,它只是將屬性統一封裝成了一個BeanMetadataAttribute
,然後就調用了父類的方法,將其放入到map中。
我們的AbstractBeanDefinition
通過繼承了BeanMetadataAttributeAccessor
這個類,可以對BeanDefinition
中的屬性進行操作。這裏說的屬性僅僅指的是BeanDefinition
中的一個map,而不是它的其它字段。
爲什麼需要AbstractBeanDefinition?
對比BeanDefinition
的源碼我們可以發現,AbstractBeanDefinition
對BeanDefinition
的大部分方法做了實現(沒有實現parentName
相關方法)。同時定義了一系列的常量及默認字段。這是因爲BeanDefinition
接口過於頂層,如果我們依賴BeanDefinition
這個接口直接去創建其實現類的話過於麻煩,所以通過AbstractBeanDefinition
做了一個下沉,並給很多屬性賦了默認值,例如:
// 默認情況不是懶加載的
private boolean lazyInit = false;
// 默認情況不採用自動注入
private int autowireMode = AUTOWIRE_NO;
// 默認情況作爲自動注入的候選bean
private boolean autowireCandidate = true;
// 默認情況不作爲優先使用的bean
private boolean primary = false;
........
這樣可以方便我們創建其子類,如我們接下來要講的:ChildBeanDefinition
,RootBeanDefinition
等等
3.AbstractBeanDefinition的三個子類
GenericBeanDefinition
- 替代了原來的
ChildBeanDefinition
,比起ChildBeanDefinition
更爲靈活,ChildBeanDefinition
在實例化的時候必須要指定一個parentName
,而GenericBeanDefinition
不需要。我們通過註解配置的bean以及我們的配置類(除@Bena
外)的BeanDefiniton
類型都是GenericBeanDefinition
。
ChildBeanDefinition
- 現在已經被
GenericBeanDefinition
所替代了。我在5.1.x
版本沒有找到使用這個類的代碼。
RootBeanDefinition
- Spring在啓動時會實例化幾個初始化的
BeanDefinition
,這幾個BeanDefinition
的類型都爲RootBeanDefinition
- Spring在合併
BeanDefinition
返回的都是RootBeanDefinition
- 我們通過
@Bean
註解配置的bean,解析出來的BeanDefinition
都是RootBeanDefinition
(實際上是其子類ConfigurationClassBeanDefinition
)
4.AnnotatedBeanDefinition
這個接口繼承了我們的BeanDefinition
接口,我們查看其源碼可以發現:
AnnotationMetadata getMetadata();
@Nullable
MethodMetadata getFactoryMethodMetadata();
這個接口相比於BeanDefinition
, 僅僅多提供了兩個方法
getMetadata()
,主要用於獲取註解元素據。從接口的命名上我們也能看出,這類主要用於保存通過註解方式定義的bean所對應的BeanDefinition
。所以它多提供了一個關於獲取註解信息的方法getFactoryMethodMetadata()
,這個方法跟我們的@Bean
註解相關。當我們在一個配置類中使用了@Bean
註解時,被@Bean
註解標記的方法,就被解析成了FactoryMethodMetadata
。
5.AnnotatedBeanDefinition的三個實現類
AnnotatedGenericBeanDefinition
- 通過形如下面的API註冊的bean都是
AnnotatedGenericBeanDefinition
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(Config.class);
}
這裏的config
對象,最後在Spring容器中就是一個AnnotatedGenericBeanDefinition
。
- 通過
@Import
註解導入的類,最後都是解析爲AnnotatedGenericBeanDefinition
。
ScannedGenericBeanDefinition
- 都過註解掃描的類,如
@Service
,@Compent
等方式配置的Bean都是ScannedGenericBeanDefinition
ConfigurationClassBeanDefinition
- 通過
@Bean
的方式配置的Bean爲ConfigurationClassBeanDefinition
最後,我們還剩一個ClassDerivedBeanDefinition
,這個類是跟kotlin
相關的類,一般用不到,筆者也不熟,這裏就不管了!
總結
至此,我們算完成了BeanDefinition
部分的學習,在下一節中,我將繼續跟大家一起學習BeanDefinition
合併的相關知識。這篇文章中,主要學習了
- 什麼是
BeanDefinition
,總結起來就是一句話,Spring創建bean時的建模對象。 BeanDefinition
的具體使用的子類,以及Spring在哪些地方使用到了它們。這部分內容在後面的學習中很重要,畫圖總結如下:
1.5小結內容的補充
單例
一個單例的bean意味着,這個bean只會容器創建一次。在創建後,容器中的每個地方使用的都是同一個bean對象。這裏用Spring官網上的一個原圖:
在上面圖片的例子中,accountDao
在被其它三個bean引用,這三個引用指向的都是同一個bean。
在默認情況下,Spring中bean的默認域就是單例的。分XML跟註解兩種配置方式:
<!--即使配置singleton也是單例的,這是Spring的默認配置-->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
@Component
// 這裏配置singleton,默認就是singleton
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class LuBanService{
}
原型
一個原型的bean意味着,每次我們使用時都會重新創建這個bean。
在上面圖片的例子中,accountDao
在被其它三個bean引用,這三個引用指向的都是一個新建的bean。
兩種配置方式:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
@Component
// 這裏配置prototype
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class LuBanService{
}
掃描下方二維碼,關注我的公衆號,更多精彩文章在等您!~~