Spring 源碼第四彈!深刻理解 BeanDefinition

積少成多!不知不覺 Spring 源碼已經更到第五篇啦~java

看源碼以前,要先會用功能,這是最基本的,由於在源碼講解中,默認你們已經熟知 Spring 基本用法了,若是還不熟悉 Spring 的基本用法,能夠看一下鬆哥發佈在 B 站上的免費入門視頻:www.bilibili.com/video/BV1Wv…spring

上篇文章和小夥伴們介紹了 Spring 源碼中的 EntityResolver,這個是用來解決 XML 文件校驗問題的。ide

接下來原本應該接着第二彈的 XML 文件解析流程繼續往下走了,考慮到接下來咱們會涉及到一個重要的概念 BeanDefinition,而有的小夥伴對此可能還不熟悉,所以本文鬆哥就先來和你們捋一捋 BeanDefinition 是什麼東西!測試

本文是 Spring 源碼解讀第五篇,閱讀本系列前面文章有助於更好的理解本文:this

  1. Spring 源碼解讀計劃
  2. Spring 源碼第一篇開整!配置文件是怎麼加載的?
  3. Spring 源碼第二彈!XML 文件解析流程
  4. Spring 源碼第三彈!EntityResolver 是個什麼鬼?

1.BeanDefinition

在 Spring 容器中,咱們普遍使用的是一個一個的 Bean,BeanDefinition 從名字上就能夠看出是關於 Bean 的定義。spa

事實上就是這樣,咱們在 XML 文件中配置的 Bean 的各類屬性,這些屬性不只僅是和對象相關,Spring 容器還要解決 Bean 的生命週期、銷燬、初始化等等各類操做,咱們定義的關於 Bean 的生命週期、銷燬、初始化等操做總得有一個對象來承載,那麼這個對象就是 BeanDefinition。代理

XML 中定義的各類屬性都會先加載到 BeanDefinition 上,而後經過 BeanDefinition 來生成一個 Bean,從這個角度來講,BeanDefinition 和 Bean 的關係有點相似於類和對象的關係。code

要理解 BeanDefinition,咱們從 BeanDefinition 的繼承關係開始看起。cdn

BeanDefinition 是一個接口,繼承自 BeanMetadataElement 和 AttributeAccessor 接口。視頻

  • BeanMetadataElement:該接口只有一個方法 getSource,該方法返回 Bean 的來源。
  • AttributeAccessor:該接口主要規範了問任意對象元數據的方法。

咱們來看下 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();
}
複製代碼

這裏定義了元數據的訪問接口,具體的實現則是 AttributeAccessorSupport,這些數據採用 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 中的方法雖然多,可是結合咱們平時在 XML 中的配置,這些方法其實都很好理解:

  1. 首先一開始定義了兩個變量用來描述 Bean 是否是單例的,後面的 setScope/getScope 方法能夠用來修改/獲取 scope 屬性。
  2. ROLE_xxx 用來描述一個 Bean 的角色,ROLE_APPLICATION 表示這個 Bean 是用戶本身定義的 Bean;ROLE_SUPPORT 表示這個 Bean 是某些複雜配置的支撐部分;ROLE_INFRASTRUCTURE 表示這是一個 Spring 內部的 Bean,經過 setRole/getRole 能夠修改。
  3. setParentName/getParentName 用來配置 parent 的名稱,這塊可能有的小夥伴使用較少,這個對應着 XML 中的 <bean parent=""> 配置。
  4. setBeanClassName/getBeanClassName 這個就是配置 Bean 的 Class 全路徑,對應 XML 中的 <bean class=""> 配置。
  5. setLazyInit/isLazyInit 配置/獲取 Bean 是否懶加載,這個對應了 XML 中的 <bean lazy-init=""> 配置。
  6. setDependsOn/getDependsOn 配置/獲取 Bean 的依賴對象,這個對應了 XML 中的 <bean depends-on=""> 配置。
  7. setAutowireCandidate/isAutowireCandidate 配置/獲取 Bean 是不是自動裝配,對應了 XML 中的 <bean autowire-candidate=""> 配置。
  8. setPrimary/isPrimary 配置/獲取當前 Bean 是否爲首選的 Bean,對應了 XML 中的 <bean primary=""> 配置。
  9. setFactoryBeanName/getFactoryBeanName 配置/獲取 FactoryBean 的名字,對應了 XML 中的 <bean factory-bean=""> 配置,factory-bean 鬆哥在以前的入門視頻中講過,小夥伴們能夠參考這裏:www.bilibili.com/video/BV1Wv…
  10. setFactoryMethodName/getFactoryMethodName 和上一條成對出現的,對應了 XML 中的 <bean factory-method=""> 配置,再也不贅述。
  11. getConstructorArgumentValues 返回該 Bean 構造方法的參數值。
  12. hasConstructorArgumentValues 判斷上一條是不是空對象。
  13. getPropertyValues 這個是獲取普通屬性的集合。
  14. hasPropertyValues 判斷上一條是否爲空對象。
  15. setInitMethodName/setDestroyMethodName 配置 Bean 的初始化方法、銷燬方法。
  16. setDescription/getDescription 配置/返回 Bean 的描述。
  17. isSingleton Bean 是否爲單例。
  18. isPrototype Bean 是否爲原型。
  19. isAbstract Bean 是否抽象。
  20. getResourceDescription 返回定義 Bean 的資源描述。
  21. getOriginatingBeanDefinition 若是當前 BeanDefinition 是一個代理對象,那麼該方法能夠用來返回原始的 BeanDefinition 。

這個就是 BeanDefinition 的定義以及它裏邊方法的含義。

2.BeanDefinition 實現類

上面只是 BeanDefinition 接口的定義,BeanDefinition 還擁有諸多實現類,咱們也來大體瞭解下。

先來看一張繼承關係圖:

這麼多實現類看着有點眼花繚亂,不過搞清楚了每個接口和類的做用,再看就很容易了。

2.1 AbstractBeanDefinition

AbstractBeanDefinition 是一個抽象類,它根據 BeanDefinition 中定義的接口提供了相應的屬性,並實現了 BeanDefinition 中定義的一部分方法。BeanDefinition 中本來只是定義了一系列的 get/set 方法,並無提供對應的屬性,在 AbstractBeanDefinition 中將全部的屬性定義出來了。

後面其餘的實現類也基本上都是在 AbstractBeanDefinition 的基礎上完成的。

2.2 RootBeanDefinition

這是一個比較經常使用的實現類,對應了通常的元素標籤。

2.3 ChildBeanDefinition

可讓子 BeanDefinition 定義擁有從父 BeanDefinition 那裏繼承配置的能力。

2.4 GenericBeanDefinition

GenericBeanDefinition 是從 Spring2.5 之後新加入的 BeanDefinition 實現類。GenericBeanDefinition 能夠動態設置父 Bean,同時兼具 RootBeanDefinition 和 ChildBeanDefinition 的功能。

2.5 AnnotatedBeanDefinition

表示註解類型 BeanDefinition,擁有獲取註解元數據和方法元數據的能力。

2.6 AnnotatedGenericBeanDefinition

使用了 @Configuration 註解標記配置類會解析爲 AnnotatedGenericBeanDefinition。

3.實踐

理論講了這麼多,接下來咱們經過幾行代碼來實踐下,驗證一下咱們前面所說的對不對。

首先項目中添加 spring-context 依賴,以下:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
複製代碼

而後咱們來建立一個 User 類,以下:

public class User {
    private String username;
    private String address;

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
複製代碼

接下來咱們先來驗證 RootBeanDefinition。咱們本身純手工定義一個 RootBeanDefinition,而且將之註冊到 Spring 容器中去。

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("username", "javaboy");
pvs.add("address", "www.javaboy.org");
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class, null, pvs);
ctx.registerBeanDefinition("user",rootBeanDefinition);
ctx.refresh();
User bean = ctx.getBean(User.class);
System.out.println(bean);
複製代碼

MutablePropertyValues 是定義對象中的一個一個屬性,構造 RootBeanDefinition 的時候,咱們傳入了類名稱和屬性集合,最終把 rootBeanDefinition 註冊到容器中去。剩下的事情由容器完成,而後咱們就能夠從容器中獲取到 User 對象了。

最終輸出結果以下:

User{username='javaboy', address='www.javaboy.org'}
複製代碼

看了這個例子,小夥伴們應該可以大體明白,咱們在 XML 中定義的各類屬性,就是先被解析到 BeanDefinition 中,而後再註冊到 Spring 容器中去,最後拿到咱們須要的 Bean。

ChildBeanDefinition 具備從父 Bean 繼承數據的能力,咱們來看下這個怎麼用。

首先新建一個 Person 類,Person 類在 User 類的基礎上增長一個 nickname 屬性,這樣 Person 就能夠繼承到 User 的 username 和 address 兩個屬性的值了:

public class Person {
    private String username;
    private String address;
    private String nickname;

    @Override
    public String toString() {
        return "Person{" +
                "username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", nickname='" + nickname + '\'' +
                '}';
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }
}
複製代碼

接下來自定義 ChildBeanDefinition:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("username", "javaboy");
pvs.add("address", "www.javaboy.org");
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class, null, pvs);
ctx.registerBeanDefinition("user",rootBeanDefinition);
ChildBeanDefinition childBeanDefinition = new ChildBeanDefinition("user");
childBeanDefinition.setBeanClass(Person.class);
childBeanDefinition.getPropertyValues().add("nickname", "江南一點雨");
ctx.registerBeanDefinition("person", childBeanDefinition);
ctx.refresh();
User user = ctx.getBean(User.class);
Person person = ctx.getBean(Person.class);
System.out.println("user = " + user);
System.out.println("person = " + person);
複製代碼

首先定義 RootBeanDefinition 並註冊到 Spring 容器中,而後再定義 ChildBeanDefinition,ChildBeanDefinition 繼承了 RootBeanDefinition 中現有的屬性值。

最後咱們從 Spring 容器中獲取 User 和 Person,打印結果以下:

user = User{username='javaboy', address='www.javaboy.org'}
person = Person{username='javaboy', address='www.javaboy.org', nickname='江南一點雨'}
複製代碼

能夠看到,Person 確實繼承了 User 的屬性值。

RootBeanDefinition 和 ChildBeanDefinition 均可以被 GenericBeanDefinition 代替,效果同樣,以下:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("username", "javaboy");
pvs.add("address", "www.javaboy.org");
GenericBeanDefinition rootBeanDefinition = new GenericBeanDefinition();
rootBeanDefinition.setBeanClass(User.class);
rootBeanDefinition.setPropertyValues(pvs);
ctx.registerBeanDefinition("user",rootBeanDefinition);
GenericBeanDefinition childBeanDefinition = new GenericBeanDefinition();
childBeanDefinition.setParentName("user");
childBeanDefinition.setBeanClass(Person.class);
childBeanDefinition.getPropertyValues().add("nickname", "江南一點雨");
ctx.registerBeanDefinition("person", childBeanDefinition);
ctx.refresh();
User user = ctx.getBean(User.class);
Person person = ctx.getBean(Person.class);
System.out.println("user = " + user);
System.out.println("person = " + person);
複製代碼

運行結果以下:

user = User{username='javaboy', address='www.javaboy.org'}
person = Person{username='javaboy', address='www.javaboy.org', nickname='江南一點雨'}
複製代碼

能夠看到,和前面的運行效果一致。

在咱們本系列前面文章(Spring 源碼第一篇開整!配置文件是怎麼加載的?)的案例中,默認使用的也是 GenericBeanDefinition,以下:

如今 Spring Boot 普遍流行以後,Java 配置使用愈來愈多,以 @Configuration 註解標記配置類會被解析爲 AnnotatedGenericBeanDefinition;以 @Bean 註解標記的 Bean 會被解析爲 ConfigurationClassBeanDefinition。

咱們新建一個 MyConfig 配置類,以下:

@Configuration
public class MyConfig {
    @Bean
    User user() {
        return new User();
    }
}
複製代碼

查看獲取到的 BeanDefinition 結果以下:

而其餘 @Service、@Controller、@Repository 以及 @Component 等註解標記的 Bean 則會被識別爲 ScannedGenericBeanDefinition。這個我就不一一演示了,小夥伴們能夠自行測試哦。

4.小結

好啦,今天主要是和小夥伴們介紹一下 Spring 中的 BeanDefinition。經過上面的原理+案例,相信小夥伴們已經明白,咱們經過 XML 或者 Java 註解配置的 Bean,咱們定義的東西會先被解析成 BeanDefinition,而後再經過 BeanDefinition 來生成咱們所須要的 Bean。

下篇文章咱們就來看這個 BeanDefinition 究竟是怎麼從 XML 文件中生成的。

好啦,小夥伴們若是以爲有收穫,記得點個在看鼓勵下鬆哥哦~

相關文章
相關標籤/搜索