Spring中的眼花繚亂的BeanDefinition

本篇博客主要參考:Spring官網閱讀(四)BeanDefinition(上)

碎碎念

在某課網看到很多培訓課程,有Java架構師、大前端架構師、大數據架構師等等,銷量都不錯,但是數據結構與算法、計算機基礎這兩門課程的銷量少的可憐,特別是計算機基礎。因爲學個XX架構師,辛苦三個月,可以學習很多有用的組件,看很多源碼,知道高併發系統如何設計,雖然只是學個皮毛,學個理論,不過薪資可能會有很大提升,但是學習計算機基礎,可能一年下來,薪資可能也不會有提升,但是隨着時間的流逝,計算機基礎的重要性就凸顯出來了。相信大家都知道一句話:勿在浮沙築高臺,但是願意在基礎上花時間、花精力的人還是不多(雖然我也是)。

引入主題

爲什麼要讀Spring源碼,有的人爲了學習Spring中的先進思想,也有的人是爲了更好的理解設計模式,當然也有很大一部分小夥伴是爲了應付面試,Spring Bean的生命週期啦,Spring AOP的原理啦,Spring IoC的原理啦,應付面試,看幾篇博客,對照着看看源碼,應該就沒什麼問題了,但是如果想真正的玩懂Spring,需要花的時間真的很多,需要你沉下心,從最基礎的看起,今天我們就來看看Spring中的基礎——BeanDefinition。

什麼是BeanDefinition


Spring官網中有詳細的說明,我們來翻譯下:
SpringIoc容器管理一個Bean或多個Bean,這些Bean通過我們提供給容器的配置元數據被創建出來(例如,在xml中<bean/>的定義)
在容器中,這些Bean的定義用BeanDefinition對象來表示,包含以下元數據:

  • 全限定類名, 通常是Bean的實際實現類;
  • Bean行爲配置元素,它們說明Bean在容器中的行爲(作用域、生命週期回調等等);
  • Bean執行工作所需要的的其他Bean的引用,這些Bean也稱爲協作者或依賴項;
  • 其他配置信息,例如,管理連接池的bean中,限制池的大小或者使用的連接的數量。

Spring官網中對BeanDefinition的解釋還是很詳細的,但是不是那麼通俗易懂,其實BeanDefinition是比較容易解釋的:BeanDefinition就是用來描述一個Bean或者BeanDefinition就是Bean的定義。

創建一個Java Bean,大概是下面這個醬紫:



我們寫的Java文件,會編譯爲Class文件,運行程序,類加載器會加載Class文件,放入JVM的方法區,我們就可以愉快的new對象了。

創建一個Spring Bean,大概是下面這個醬紫:



我們寫的Java文件,會編譯爲Class文件,運行程序,類加載器會加載Class文件,放入JVM的方法區,這一步還是保持不變(當然這個也沒辦法變。。。)
下面就是Spring的事情了,Spring會解析我們的配置類(配置文件),假設現在只配置了A,解析後,Spring會把A的BeanDefinition放到一個map中去,隨後,由一個一個的BeanPostProcessor進行加工,最終把經歷了完整的Spring生命週期的Bean放入了singleObjects。

BeanDefinition類圖鳥瞰


大家可以看到,Spring中BeanDefinition的類圖還是相當複雜的,我剛開始讀Spring源碼的時候,覺得BeanDefinition應該是一個特別簡單的東西,但是後面發覺並不是那麼回事。

下面我將對涉及到的類逐個進行解讀。

AttributeAccessor

AttributeAccessor是一個接口:

/**
 * Interface defining a generic contract for attaching and accessing metadata
 * to/from arbitrary objects.
 *
 * @author Rob Harrop
 * @since 2.0
 */
public interface AttributeAccessor {
    void setAttribute(String name, @Nullable Object value);
    Object getAttribute(String name);
    Object removeAttribute(String name);
    boolean hasAttribute(String name);
    String[] attributeNames();
}

我們來看下類上面的註釋:接口定義了通用的方法來保存或者讀取元數據。既然是接口,那麼一定會有實現類,我們先把這個放一邊。

BeanMetadataElement

BeanMetadataElement也是一個接口,裏面只定義了一個方法:

/**
 * Interface to be implemented by bean metadata elements
 * that carry a configuration source object.
 *
 * @author Juergen Hoeller
 * @since 2.0
 */
public interface BeanMetadataElement {
    @Nullable
    Object getSource();

}

我們還是來看下類上的註釋:接口提供了一個方法來獲取Bean的源對象,這個源對象就是源文件,怎麼樣,是不是不太好理解,沒關係,我們馬上寫個代碼來看下:

@Configuration
@ComponentScan
public class AppConfig {
}
@Service
public class BookService {
}
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(context.getBeanDefinition("bookService").getSource());
    }
}
file [D:\cycleinject\target\classes\com\codebear\springcycle\BookService.class]

怎麼樣,現在理解了把。

AttributeAccessorSupport

AttributeAccessorSupport類是一個抽象類,實現了AttributeAccessor接口,這個AttributeAccessor還記得吧,裏面定義了通用的方法來保存或者讀取元數據的虛方法,AttributeAccessorSupport便實現了這個虛方法,AttributeAccessorSupport定義了一個map容器,元數據就被保存在這個map裏面。

爲什麼要有這個map

初次讀Spring源碼,看到這個map的時候,覺得有點奇怪,元數據不應該是保存在BeanDefinition的beanClass、scope、lazyInit這些字段裏面嗎?這個map不是多次一舉嗎?

後面才知道,Spring是爲了方便擴展,不然BeanDefinition有新的特性,就要新增字段,這是其一;其二,如果程序員要擴展Spring,而BeanDefinition中定義的字段已經無法滿足擴展了呢?

那Spring自己有使用這個map嗎,答案是有的,我們來看下,Spring在這個map中放了什麼數據:

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        BeanDefinition appConfig = context.getBeanDefinition("appConfig");
        for (String item : appConfig.attributeNames()) {
            System.out.println(item + ":" + appConfig.getAttribute(item));
        }
    }
org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass:full
org.springframework.aop.framework.autoproxy.AutoProxyUtils.preserveTargetClass:true

可以看到,Spring在裏面放了兩個item:

  • 第一個item保存着這個配置類是否是一個Full配置類,關於Full配置類,我在先前的博客有簡單的介紹過:Spring中你可能不知道的事(二)
  • 第二個item,從名字上就可以知道和AOP相關。

BeanDefinition

BeanDefinition是一個接口,繼承了AttributeAccessor、BeanMetadataElement,這兩個類上面已經介紹過了。

BeanDefinition定義了很多方法,比如setBeanClassName、getBeanClassName、setScope、getScope、setLazyInit、isLazyInit等等,這些方法一眼就知道是什麼意思了,這裏就不解釋了。

BeanMetadataAttributeAccessor

BeanMetadataAttributeAccessor繼承了AttributeAccessorSupport,對保存或者讀取元數據的方法進行了進一步的封裝。

AbstractBeanDefinition

AbstractBeanDefinition是一個抽象類,繼承了BeanMetadataAttributeAccessor,實現了BeanDefinition。

BeanDefinition實現了BeanDefinition定義的大部分虛方法,同時定義了很多常量和默認值。

AbstractBeanDefinition有三個子類,下面我們來看看這三個子類。

ChildBeanDefinition

從Spring2.5開始,ChildBeanDefinition已經不再使用,取而代之的是GenericBeanDefinition。

GenericBeanDefinition

GenericBeanDefinition替代了ChildBeanDefinition,ChildBeanDefinition從字面上,就可以看出有“子BeanDefinition”的意思,難道BeanDefinition還有“父子關係”嗎?當然有。

public class ChildService {
    private int id;
    private String name;

    public ChildService(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class ParentService {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public ParentService(int id, String name) {
        this.id = id;
        this.name = name;
    }
}
 public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        GenericBeanDefinition parentBeanDefinition = new GenericBeanDefinition();
        parentBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
        parentBeanDefinition.setAttribute("name", "codebear");
        parentBeanDefinition.setAbstract(true);
        parentBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(1);
        parentBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue("CodeBear");

        GenericBeanDefinition childBeanDefinition = new GenericBeanDefinition();
        childBeanDefinition.setParentName("parent");
        childBeanDefinition.setBeanClass(ChildService.class);

        context.registerBeanDefinition("parent", parentBeanDefinition);
        context.registerBeanDefinition("child", childBeanDefinition);

        context.refresh();

        BeanDefinition child = context.getBeanFactory().getMergedBeanDefinition("child");
        for (String s : child.attributeNames()) {
            System.out.println(s + ":" + child.getAttribute(s));
        }
        System.out.println("scope:" + child.getScope());

        System.out.println("-------------------");

        ChildService service = context.getBean(ChildService.class);
        System.out.println(service.getName());
        System.out.println(service.getId());
    }

運行結果:


name:codebear
scope:singleton
-------------------
CodeBear
1

來分析下代碼:

  1. 創建了GenericBeanDefinition對象parentBeanDefinition,設置爲了單例模式,設置了Attribute,聲明瞭構造方法的兩個參數值;
  2. 創建了GenericBeanDefinition對象childBeanDefinition,設置parentName爲parent,BeanClass爲ChildService;
  3. 註冊parentBeanDefinition,beanName爲parent,childBeanDefinition,beanName爲child;
  4. 刷新容器;
  5. 從mergedBeanDefinitions取出了child,mergedBeanDefinitions存放的是合併後的BeanDefinition;
  6. 打印出child的attribute、scope、構造方法的兩個參數值。

大家可以看到,childBeanDefinition繼承了parentBeanDefinition。

如果沒有父子關係,單獨作爲BeanDefinition,也可以用GenericBeanDefinition來表示:

       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        genericBeanDefinition.setBeanClass(AuthorService.class);
        genericBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        context.registerBeanDefinition("authorService", genericBeanDefinition);

        context.refresh();

        BeanDefinition mergedBeanDefinition = context.getBeanFactory().getMergedBeanDefinition("authorService");
        BeanDefinition beanDefinition = context.getBeanFactory().getMergedBeanDefinition("authorService");

        System.out.println(mergedBeanDefinition);
        System.out.println(beanDefinition);

運行結果:

Root bean: class [com.codebear.springcycle.AuthorService]; scope=prototype; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
Root bean: class [com.codebear.springcycle.AuthorService]; scope=prototype; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null

可以看到,當沒有父子關係,beanDefinition依舊會被保存在mergedBeanDefinitions中,只是存儲的內容和beanDefinitions中所存儲的內容是一模一樣的。

GenericBeanDefinition總結

GenericBeanDefinition替代了低版本Spring的ChildBeanDefinition,GenericBeanDefinition比ChildBeanDefinition、RootBeanDefinition更加靈活,既可以單獨作爲BeanDefinition,也可以作爲父BeanDefinition,還可以作爲子GenericBeanDefinition。

RootBeanDefinition

在介紹GenericBeanDefinition的時候,寫了兩段代碼。

給第一個代碼打上斷點,觀察下mergedBeanDefinitions,會發現parentBeanDefinition和
childBeanDefinition在mergedBeanDefinitions都變爲了RootBeanDefinition:


給第二個代碼打上斷點,也觀察下mergedBeanDefinitions,會發現authorService在mergedBeanDefinitions也變爲了RootBeanDefinition:


可以看到在mergedBeanDefinitions存放的都是RootBeanDefinition。

RootBeanDefinition也可以用來充當父BeanDefinition,就像下面的醬紫:

 public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        RootBeanDefinition genericBeanDefinition = new RootBeanDefinition();
        genericBeanDefinition.setBeanClass(ParentService.class);
        genericBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        context.registerBeanDefinition("parent", genericBeanDefinition);

        GenericBeanDefinition rootBeanDefinition = new GenericBeanDefinition();
        rootBeanDefinition.setBeanClass(ChildService.class);
        rootBeanDefinition.setParentName("parent");

        context.refresh();
    }

但是RootBeanDefinition不可以充當子BeanDefinition:

  public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        RootBeanDefinition genericBeanDefinition = new RootBeanDefinition();
        genericBeanDefinition.setBeanClass(ParentService.class);
        genericBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        context.registerBeanDefinition("parent", genericBeanDefinition);

        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
        rootBeanDefinition.setBeanClass(ChildService.class);
        rootBeanDefinition.setParentName("parent");

        context.refresh();
    }

運行結果:

Exception in thread "main" java.lang.IllegalArgumentException: Root bean cannot be changed into a child bean with parent reference
    at org.springframework.beans.factory.support.RootBeanDefinition.setParentName(RootBeanDefinition.java:260)
    at com.codebear.springcycle.Main.main(Main.java:20)

拋出了異常。

查詢源碼:

    @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");
        }
    }

發現調用RootBeanDefinition的setParentName方法,直接拋出了異常。

RootBeanDefinition總結

RootBeanDefinition可以作爲其他BeanDefinition的父BeanDefinition,也可以單獨作爲BeanDefinition,但是不能作爲其他BeanDefinition的子BeanDefinition,在mergedBeanDefinitions存儲的都是RootBeanDefinition。

ScannedGenericBeanDefinition

@Configuration
@ComponentScan
public class AppConfig {
}
@Service
public class AuthorService {
}
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(context.getBeanDefinition("authorService").getClass());
    }
}

運行結果:

class org.springframework.context.annotation.ScannedGenericBeanDefinition

通過註解掃描出來的Bean的BeanDefinition用ScannedGenericBeanDefinition來表示。

AnnotatedGenericBeanDefinition

 public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(context.getBeanDefinition("appConfig").getClass());
    }

運行結果:

class org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition

配置類的BeanDefinition用AnnotatedGenericBeanDefinition來表示。

ConfigurationClassBeanDefinition

public class AuthorService {
}
@Configuration
@ComponentScan
public class AppConfig {
    @Bean
    public AuthorService authorService() {
        return null;
    }
}
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
 System.out.println(context.getBeanDefinition("authorService").getClass());
    }

運行結果:

  class org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$ConfigurationClassBeanDefinition

用@Bean聲明的Bean的BeanDefinition用ConfigurationClassBeanDefinition來表示。

是不是完全沒想到,一個BeanDefinition可以牽涉到這麼多的內容,這些內容說沒用,確實沒什麼用;說有用,也有用。不明白這些內容,閱讀Spring源碼會比較懵逼,爲什麼會有那麼多的BeanDefinition。這個時候,你就會卡殼,拼命的想弄懂這些BeanDefinition都是用來幹嘛的,但是網上關於BeanDefinition的博客不算太多,比較好的博客就更少了,希望此篇文章可以填充這塊空白。

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