Spring官網閱讀(四)BeanDefinition(上)

前面幾篇文章已經學習了官網中的1.2,1.3,1.4三小結,主要是容器,Bean的實例化及Bean之間的依賴關係等。這篇文章,我們繼續官網的學習,主要是BeanDefinition的相關知識,這是Spring中非常基礎的一塊內容,也是我們閱讀源碼的基石。本文主要涉及到官網中的1.31.5中的一些補充知識。同時爲我們1.7小節中BeanDefinition的合併做一些鋪墊

BeanDefinition是什麼?

我們先看官網上是怎麼解釋的:

在這裏插入圖片描述

從上文中,我們可以得出以下幾點結論:

  1. BeanDefinition包含了我們對bean做的配置,比如XML<bean/>標籤的形式進行的配置
  2. 換而言之,Spring將我們對bean的定義信息進行了抽象,抽象後的實體就是BeanDefinition,並且Spring會以此作爲標準來對Bean進行創建
  3. 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的源碼我們可以發現,AbstractBeanDefinitionBeanDefinition的大部分方法做了實現(沒有實現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合併的相關知識。這篇文章中,主要學習了

  1. 什麼是BeanDefinition,總結起來就是一句話,Spring創建bean時的建模對象。
  2. 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{
    
}

掃描下方二維碼,關注我的公衆號,更多精彩文章在等您!~~

公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章