【修煉內功】[spring-framework] [3] Bean是如何被建立的

本文已收錄 【修煉內功】躍遷之路

spring-framework.jpg

林中小舍.png

書接上文,在 BeanDefinitionReader 一文中簡單介紹了 XMLBeanFactory解析xml配置、並註冊 BeanDefinition的邏輯,本文就bean的實例化過程及銷燬作簡要分析

先放一張大圖(點擊圖片放大查看,右鍵或長按保存後更清晰),展現完整的bean建立過程及銷燬過程,若是對spring原理有一些理解可將此圖做爲開發過程當中的參考,若是對spring原理還有一些模糊可繼續向下閱讀(長文預警!)java

Spring.Bean.Factory

BeanDefinition

經過前文簡單瞭解到,Spring在初始化過程當中並非直接實例化bean,而是先收集全部bean的元數據信息並註冊,bean的元數據描述爲接口BeanDefinition,該接口定義了你能想到的一切有關bean的屬性信息web

BeanDefinition衍生出一系列實現類spring

Beans.BeanDefinition

  • AbstractBeanDefinition

    如同其餘Spring類,大部分BeanDefinition接口的邏輯都由該抽象類實現sql

  • GenericBeanDefinition

    GenericBeanDefinition是一站式、用戶可見的bean definition,如何理解「用戶可見」?數據庫

    可見的bean definition意味着能夠在該bean definition上定義post-processor來對bean進行操做segmentfault

  • RootBeanDefinition

    當bean definition存在父子關係的時候,RootBeanDefinition用來承載父元數據的角色(也可獨立存在),同時它也做爲一個可合併的bean definition使用,在Spring初始化階段,全部的bean definition均會被(向父級)合併爲RootBeanDefinition,子bean definition(GenericBeanDefinition/ChildBeanDefinition)中的定義會覆蓋其父bean definition(由parentName指定)的定義緩存

  • ChildBeanDefinition

    當bean definition存在父子關係的時候,ChildBeanDefinition用來承載子元數據的角色(也可獨立存在),在Spring推出GenericBeanDefinition後,其徹底能夠被GenericBeanDefinition替代,目前使用場景已經很是少session

  • AnnotatedBeanDefinitionapp

    如其名,主要用來定義註解場景的bean definition框架

    • ScannedGenericBeanDefinition

      主要用來定義@Component@Service等bean definition,其AnnotationMetadata metadata屬性用來存儲該bean的類註解信息

    • AnnotatedGenericBeanDefinition

      ScannedGenericBeanDefinition不一樣的是,其主要用來定義@Configuration等配置類中@Bean的bean definition,其AnnotationMetadata metadata屬性與ScannedGenericBeanDefinition相同,MethodMetadata factoryMethodMetadata屬性用來存儲@Bean描述的方法信息

BeanDefinitionHolder只是簡單捆綁了BeanDefinition、bean-name、bean-alias,用於註冊BeanDefinition及別名alias

BeanRegistry

在通常工程中,bean的定義分散在各類地方(尤爲使用註解以後),Spring並不能在解析出每個bean的元數據信息後當即對該bean作實例化動做,對於依賴的分析與注入、類(方法)的代理、功能上的擴展等,必須等全部的bean元數據所有解析完成以後才能進行

在bean元數據解析完成以後、bean實例化以前,對bean的元數據信息有一個暫存的過程,這個過程即是bean的註冊

bean的註冊邏輯分兩步,一爲BeanDefinition的註冊,一爲別名的註冊

  • BeanDefinition註冊的定義在BeanDefinitionRegistry#registerBeanDefinition,其實現使用一個Map<String, BeanDefinition>來保存bean-name和BeanDefinition的關係
  • 別名的註冊定義在AliasRegistry#registerAlias,其實現一樣使用一個Map<String, String>來保存別名alias-name和bean-name(或另外一個別名alias-name)的關係

在完成bean的元數據註冊以後,即是根據詳盡的元數據信息進行實例化了

BeanFactory

bean的實例化過程比較複雜(Spring考慮到了各類場景),附上BeanRegistry&BeanFactory相關的類依賴圖

Beans.ListableBeanFactory

初看此圖,請不要失去信心和耐心,圖中各種的做用會一一講解(見 #附錄#相關類說明),這裏先介紹幾個核心的接口

  • AliasRegistry

    bean別名註冊和管理

  • BeanDefinitionRegistry

    bean元數據註冊和管理

  • SingletonBeanRegistry

    單例bean註冊和管理

  • BeanFactory

    bean工廠,提供各類bean的獲取及判斷方法

大體瀏覽上圖(類依賴圖)不難發現,核心實現落在了DefaultListableBeanFactory,咱們以此類做爲切入點分析bean實例化過程

bean的實例化過程發生在getBean調用階段(對於singleton則發生在首次調用階段),getBean的實現方法衆多,咱們追根溯源,找到最通用的方法AbstractBeanFactory#doGetBean

// org.springframework.beans.factory.support.AbstractBeanFactory
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
        @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

    // 1. 獲取真正的beanName
    final String beanName = transformedBeanName(name);
    Object bean;

    // 2. 嘗試獲取(提早曝光的)singleton bean實例(爲了解決循環依賴)
    Object sharedInstance = getSingleton(beanName);
    
    // 3. 若是存在
    if (sharedInstance != null && args == null) { ... }
    
    // 4. 若是不存在
    else { ... }
    
    // 5. 嘗試類型轉換
    if (requiredType != null && !requiredType.isInstance(bean)) { ... }
    
    return (T) bean;
}

bean的實例化過程雖然複雜,但大致邏輯很是清楚

Flowchart.doGetBean

接下,就以上五個子流程(藍色部分)一一展開

在實例化bean的過程中,Spring會使用大量的中間態來判斷、處理各類場景和狀況,此處先行列出Spring所使用的一些關鍵的中間態(各中間態的做用會在下文介紹,見 #附錄#中間態說明),以便在下文中更好地理解bean實例化過程當中對各類狀況的判斷和處理邏輯

Spring.Bean.Cache

bean name轉換

在使用bean-name獲取bean的時候,除了可使用原始bean-name以外,還可使用alias別名等,bean-name的轉換則是將傳入的‘bean-name’一層層轉爲最原始的bean-name

Return the bean name, stripping out the factory dereference prefix if necessary, and resolving aliases to canonical names.
protected String transformedBeanName(String name) {
    return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

函數canonicalName的做用則是利用別名註冊aliasMap,將別名alias轉爲原始bean-name

函數transformedBeanName比較特殊,其是將FactoryBean的bean-name前綴 '&' 去除(BeanFactory#FACTORY_BEAN_PREFIX 下文會介紹)

Flowchart.transformedBeanName

嘗試獲取單例

拿到原始的bean-name以後,即可以實例化bean或者直接獲取已經實例化的singleton-bean,此處爲何叫‘嘗試’獲取呢?

在獲取singleton-bean的時候通常存在三種狀況:1. 還未實例化(或者不是單例);2. 已經實例化;3. 正在實例化;

  • 對於 「1. 還未實例化」 ,返回null便可,後續進行實例化動做
  • 對於「2. 已經實例化」,直接返回實例化的singleton-bean
  • 對於「3. 正在實例化」,則較難理解

Spring中對於singleton-bean,有一個sharedInstance的概念,在調用getSingleton函數時,返回的不必定是徹底實例化的singleton-bean,有多是一箇中間狀態(建立完成,但未進行屬性依賴注入及其餘後處理邏輯),這種中間狀態會經過getSingleton函數提早曝光出來,目的是爲了解決循環依賴(下文會詳細介紹循環依賴)

Beans.CircularReferences

在實例化 beanA的過程當中,須要依賴 beanBbeanC,若是 beanC同時又依賴 beanA,則須要 beanA在實例化完成以前提早曝光出來,以避免形成 beanA等待 beanC實例化完成, beanC等待 beanA實例化完成,相似一種死鎖的狀態

在繼續進行以前,有必要簡單介紹幾個中間態(詳見 #附錄#中間態說明

  • singletonObjects

    緩存已經實例化完成的singleton-bean

  • earlySingletonObjects

    緩存正在實例化的、提早曝光的singleton-bean,用於處理循環依賴

  • singletonFactories

    緩存用於生成earlySingletonObjectObjectFactory

ObjectFactory,定義了一個用於建立、生成對象實例的工廠方法

@FunctionalInterface
public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

介紹了上述以後,再來描述getSingleton的邏輯就會比較清楚

Flowchart.getSingleton

不用糾結上述中間態的值是什麼時候被設置進去的,下文會逐步說起

FactoryBean處理(sharedInstance存在的邏輯)

上述 sharedInstance 必定是咱們須要的bean實例麼?未必!

定義bean的時候能夠經過實現FactoryBean接口來定製bean實例化的邏輯,以此增長更多的靈活性及可能性(How to use the Spring FactoryBean?

@Bean(initMethod = "init", destroyMethod = "destroy")
public FactoryBean myBean() {
    return new FactoryBean<MyBean>() {
        /**
         * 定製bean初始化邏輯
         */
        @Override
        public MyBean getObject() throws Exception {
            MyBean myBean = new MyBean();
            // ... 定製化的邏輯
            return myBean;
        }

        /**
         * 真正的bean類型
         */
        @Override
        public Class<?> getObjectType() {
            return MyBean.class;
        }

        @Override
        public boolean isSingleton() {
            return true;
        }
    }
}

經過註冊FactoryBean類型的bean,實例化後的原始實例類型一樣爲FactoryBean,但咱們須要的是經過FactoryBean#getObject方法獲得的實例,這便須要針對FactoryBean作一些處理,這也是接下來要講解的函數AbstractBeanFactory#getObjectForBeanInstance

Get the object for the given bean instance, either the bean instance itself or its created object in case of a FactoryBean.

Now we have the bean instance, which may be a normal bean or a FactoryBean.
If it's a FactoryBean, we use it to create a bean instance.

該函數要實現的邏輯比較簡單,若是sharedInstanceFactoryBean,則使用getObject方法建立真正的實例

getObjectForBeanInstance是一個通用函數,並不僅針對經過 getSingleton獲得的 sharedInstance,任何經過緩存或者建立獲得的 rawInstance,都須要通過 getObjectForBeanInstance處理,拿到真正須要的 beanInstance

簡單介紹getObjectForBeanInstance函數的入參

/**
 * @param beanInstance  sharedInstance / rawInstance,可能爲FactoryBean
 * @param name            傳入的未作轉換的 bean name
 * @param beanName        對name作過轉換後的原始 canonical bean name
 * @param mbd            合併後的RootBeanDefinition,下文會介紹
 */
protected Object getObjectForBeanInstance(
    Object beanInstance, String name, String beanName, RootBeanDefinition mbd)

getObjectForBeanInstance函數的處理邏輯

Flowchart.getObjectForBeanInstance

上圖中有一個邏輯判斷,若是入參name以'&' (BeanFactory#FACTORY_BEAN_PREFIX)開頭則直接返回(BeanFactory)

這裏兼容了一種狀況,若是須要獲取/注入FactoryBean而不是getObject生成的實例,則須要在bean-name/alias-name前加入'&'

/**
 * 注入FactoryBean#getObject生成的實例
 */
@Autowired
private MyBean myBean;

/**
 * 直接注入FactoryBean
 */
@Resource(name = "&myBean")
private FactoryBean<MyBean> myFactoryBean;

對於singleton,FactoryBean#getObject的結果會被緩存到factoryBeanObjectCache,對於緩存中不存在或者不是singleton的狀況,會經過FactoryBean#getObject生成(上圖中藍色未展開的邏輯)

Spring並不是簡單的調用FactoryBean#getObject,而是分爲兩部分處理

bean instance生成

上圖中doGetObjectFromFactoryBean,主要對getObject方法進行了包裝,判斷是否須要在SecurityManager框架內執行以及對null結果進行封裝(NullBean)

Flowchart.doGetObjectFromFactoryBean

bean instance後處理

上圖中postProcessObjectFromFactoryBean,主要對生成的bean instance作一些後處理(能夠跟蹤到AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization),

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
        throws BeansException {
    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        // 拿到全部註冊的BeanPostProcessor,執行後處理動做
        Object current = processor.postProcessAfterInitialization(result, beanName);
        if (current == null) {
            return result;
        }
        result = current;
    }
    return result;
}

postProcessAfterInitialization函數能夠對現有bean instance作進一步的處理,甚至能夠返回新的bean instance,這就爲bean的加強提供了一個很是方便的擴展方式(能夠思考一下,AOP的代理類是如何生成的)

加載bean實例(sharedInstance不存在的邏輯)

以上,討論了bean instance存在於緩存中的狀況,若是緩存中不存則須要進行bean的加載

Flowchart.createBeanOverview

簡單來說,bean的加載/建立分爲三大部分

  1. 將BeanDefinition合併爲RootBeanDefinition

    這裏相似類繼承,子BeanDefinition屬性會覆蓋父BeanDefinition

  2. 依次加載所依賴的bean

    對於有依賴的狀況,優先遞歸加載依賴的bean

  3. 按照不一樣的bean類型,根據BeanDefinition的定義進行加載/建立

BeanDefinition合併(RootBeanDefinition)

將BeanDefinition轉爲RootBeanDefinition,若是存在父子關係,則進行合併

這裏再也不贅述,能夠參考 AbstractBeanFactory#getMergedLocalBeanDefinition

加載depends-on beans

String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
    // 遍歷全部的依賴
    for (String dep : dependsOn) {
        // 檢測循環依賴
        if (isDependent(beanName, dep)) { /* throw exception */ }
        // 註冊依賴關係
        registerDependentBean(dep, beanName);
        // 遞歸getBean,加載依賴bean
        try { getBean(dep); }
        catch (NoSuchBeanDefinitionException ex) { /* throw exception */ }
    }
}

邏輯很簡單,但這裏涉及到兩個中間態dependentBeanMap、dependenciesForBeanMap

  • dependentBeanMap

    存儲哪些bean依賴了我(哪些bean裏注入了我)

    若是 beanB -> beanA, beanC -> beanA,key爲beanA,value爲[beanB, beanC]

  • dependenciesForBeanMap

    存儲我依賴了哪些bean(我注入了哪些bean)

    若是 beanA -> beanB, beanA -> beanC,key爲beanA,value爲[beanB, beanC]

理解二者的存儲關係,有助於在閱讀源碼的過程當中理解bean的加載和銷燬順序

加載singleton bean實例

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        // singletonFactory - ObjectFactory
        try { return createBean(beanName, mbd, args); }
        catch (BeansException ex) {    destroySingleton(beanName);    throw ex; }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

這裏涉及兩個比較核心的函數createBeangetObjectForBeanInstance

  • createBean

    根據BeanDefinition的內容,建立/初始化bean instance

  • getObjectForBeanInstance

    上文已經介紹過,主要處理FactoryBean,將FactoryBean轉爲真正須要的bean instance

createBean被包裝在lambda(singletonFactory)中做爲getSingleton的參數,咱們來看getSingleton的實現邏輯

<img src="../notes/BeanFactory/Flowchart.createSingleton.jpg" alt="Flowchart.createSingleton" style="zoom:50%;" />

因此,關鍵的邏輯在createBean函數中,bean的建立邏輯較爲複雜,咱們放到後面介紹

加載prototype bean實例

else if (mbd.isPrototype()) {
    Object prototypeInstance = null;
    try {
        beforePrototypeCreation(beanName);
        prototypeInstance = createBean(beanName, mbd, args);
    }
    finally { afterPrototypeCreation(beanName);    }
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

prototype bean的建立與singleton bean相似,只是不會緩存建立完成的bean

Flowchart.createPrototype

加載其餘scope bean實例

scope,即做用域,或者能夠理解爲生命週期

上文介紹了singleton-bean及prototype-bean的建立過程,嚴格意義上講以上兩種都是一種特殊的scope-bean,分別對應ConfigurableBeanFactory#SCOPE_SINGLETONConfigurableBeanFactory#SCOPE_PROTOTYPE,前者做用域爲整個IOC容器,也可理解爲單例,後者做用域爲所注入的bean,每次注入(每次觸發getBean)都會從新生成

Spring中還提供不少其餘的scope,如WebApplicationContext#SCOPE_REQUESTWebApplicationContext#SCOPE_SESSION,前者做用域爲一次web request,後者做用域爲一個web session週期

自定義scope的bean實例建立過程與singleton bean的建立過程十分類似,須要實現Scopeget方法(org.springframework.beans.factory.config.Scope#get),

else {
    String scopeName = mbd.getScope();
    final Scope scope = this.scopes.get(scopeName);
    if (scope == null) { /* throw exception */ }
    try {
        Object scopedInstance = scope.get(beanName, () -> {
            beforePrototypeCreation(beanName);
            // createBean被封裝在Scope#get函數的lambda參數ObjectFactory中
            try { return createBean(beanName, mbd, args); }
            finally { afterPrototypeCreation(beanName); }
        });
        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
    } catch (IllegalStateException ex) { /* throw exception */}
}

Scope接口除了get方法以外,還有一個remove方法,前者用於定義bean的初始化邏輯,後者用於定義bean的銷燬邏輯

public interface Scope {
  /**
   * Return the object with the given name from the underlying scope
   */
  Object get(String name, ObjectFactory<?> objectFactory);
  
   /**
   * Remove the object with the given name from the underlying scope.
   */
  Object remove(String name);
}

WebApplicationContext#SCOPE_SESSION對應的Scope實現見org.springframework.web.context.request.SessionScope

WebApplicationContext#SCOPE_REQUEST對應的Scope實現見org.springframework.web.context.request.RequestScope

以上兩種Scope實現都較爲簡單,前者將初始化的bean存儲在request attribute種,後者將初始化的bean存儲在http session中,具體細節請自行查閱源碼

Q: Spring中實現了哪些Scope?又是何時註冊的?

Bean建立過程

AbstractAutowireCapableBeanFactory#createBean

瞭解bean建立的過程也是一個抽絲剝繭的過程,真正建立的過程封裝在AbstractAutowireCapableBeanFactory#doCreateBean中,而在此以前有一些準備工做,總體流程以下圖

Flowchart.createBean

  1. resolveBeanClass

    這一步驟用於鎖定bean class,在沒有顯示指定beanClass的狀況下,使用className加載beanClass

  2. 驗證method overrides

    在 [[spring-framework] [2] BeanDefinitionReader](https://segmentfault.com/a/11... 一文中有提到過lookup-method及replace-method,該步驟是爲了確認以上兩種配置中的method是否存在

  3. 執行InstantiationAwareBeanPostProcessor前處理器(postProcessBeforeInstantiation)

    這裏要特別注意的是,若是這個步驟中生成了「代理」bean instance,則會有一個短路操做,直接返回該bean instance而再也不執行doCreate,這是一個「細思極恐」的操做,但在一些特殊場景(尤爲框架之中)提供了良好的擴展機制

    try {
        // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
        Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
        if (bean != null) {
              // 若是這裏生成了代理的bean instance會直接返回
            return bean;
        }
    } cache (Throwable ex) { // throw exception }
    
    try {
      // 建立bean instance
      Object beanInstance = doCreateBean(beanName, mbdToUse, args);
      // ...
    }
    Q: InstantiationAwareBeanPostProcessor的使用場景有哪些?Spring有哪些功能使用了InstantiationAwareBeanPostProcessor?它們是在何時註冊的?
  4. doCreateBean (AbstractAutowireCapableBeanFactory)

    真正bean的建立及初始化過程在此處實現,但Spring對bean建立及初始化邏輯的複雜程度徹底超出了本篇文章之承載,這裏只對一些關鍵的邏輯作梳理

    Flowchart.doGetBean

建立bean實體

AbstractAutowireCapableBeanFactory#createBeanInstance

Flowchart.createBeanInstance

從上面的流程圖能夠看出,建立bean實體不必定會使用到構造函數,有兩個特殊的方式

1. instance supplier

AbstractAutowireCapableBeanFactory#obtainFromSupplier

從Spring5開始,多了一種以函數式註冊bean的方式(參考https://www.baeldung.com/spri...

// 註冊MyService
context.registerBean(MyService.class, () -> new MyService());
// 註冊MyService,並指定bean name
context.registerBean("mySecondService", MyService.class, () -> {
    MyService myService = new MyService();
    // 其餘的邏輯
    return myService;
});
// 註冊MyService,指定bean name,並加強bean definition
context.registerBean("myCallbackService", MyService.class, () -> {
    MyService myService = new MyService();
    // 其餘的邏輯
    return myService;
}), bd -> bd.setAutowireCandidate(false));

或者

// 構建BeanDefinition
BeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MyService.class, () -> {
    MyService myService = new MyService();
    // 其餘的邏輯
    return myService;
});
// 註冊BeanDefinition
beanFactory.registerBeanDefinition("myService", bd);

經過以上方式註冊的bean,Spring會調用該supplier生成bean實體

2. factory method

AbstractAutowireCapableBeanFactory#instantiateUsingFactoryMethod

ConstructorResolver#instantiateUsingFactoryMethod

在xml配置中還有一種不太常見的bean註冊方式

public class MyHome {
  // 靜態方法
  public static MyHome create() {
    return new MyHome();
  }
}

public class MyFactory {
  // 非靜態方法
  public MyHome create() {
    return new MyHome();
  }
}
<bean id="myFactory" class="com.manerfan.MyFactory"></bean>

<!-- 方式一 -->
<!-- 使用指定類的靜態方法 -->
<bean id="myHome1" 
      class="com.manerfan.MyHome" 
      factory-method="create"></bean>  
<!-- 方式二 -->
<!-- 使用指定bean的非靜態方法 -->
<bean id="myHome2" 
      class="com.manerfan.MyHome" 
      factory-method="create" 
      factory-bean="myFactory"></bean>

Spring會經過指定類的指定方法生成bean實體,其中有兩種方式,一種方式(僅指定factory-method)使用指定類的靜態方法生成,另外一種方式(同時指定factory-methodfactory-bean)使用指定bean的非靜態方法生成

同時factory-method中還容許傳入一些參數,若是存在同名函數,Spring會根據參數的個數及類型找到匹配的method

public class MyHome {
  private MyHouse house;
  private MyCar car;
  // setters
}

public class MyFactory {
  // 攜帶入參
  public MyHome create(MyHouse house, MyCar car) {
     MyHome myHome = new MyHome();
    myHome.setHouse(house);
    myHome.setCar(car)
    return myHome;
  }
  
  // 同名函數
  public MyHome create(MyHouse house) {
     MyHome myHome = new MyHome();
    myHome.setHouse(house);
    myHome.setCar(defaultCar)
    return myHome;
  }
}
<bean id="myHome2" 
      class="com.manerfan.MyHome" 
      factory-method="create" 
      factory-bean="myFactory">
  <!-- 這裏使用的是構造函數參數類型 -->
  <constructor-arg name="house" ref="house"/>
  <constructor-arg name="car" ref="car"/>
</bean>

這樣的代碼是否是讓你想到了@Configuration中的@Bean,@Bean所修飾方法若是存在參數的話,Spring會經過參數的類型及名稱自動進行依賴注入

@Configuration
public class MyFactory {
  @Bean
  public MyHome create(MyHouse house, MyCar car) {
    MyHome myHome = new MyHome();
    myHome.setHouse(house);
    myHome.setCar(car)
    return myHome;
  }
}

咱們能夠大膽猜想,@Configuration + @Bean的實現方式就是factory-bean + factory-method,在後文介紹Spring的註解體系時會揭曉

使用指定(類)bean的(靜態)方法建立bean實體的邏輯在ConstructorResolver#instantiate(String, RootBeanDefinition, Object, Method, args),而真正的邏輯在SimpleInstantiationStrategy#instantiate(RootBeanDefinition, String, BeanFactory, Object, Method, Object...),其核心的執行邏輯很是簡單,有了方法factoryMethod(factoryBean)及入參args,即可以調用該方法建立bean實體

Object result = factoryMethod.invoke(factoryBean, args);

factoryBean能夠經過beanFactory.getBean獲取到(正是當前在講的邏輯),factoryMethod能夠經過反射獲取到,入參args如何獲取?這便涉及到Spring中的一個重要概念 -- 依賴注入,如何準確的找到依賴的實體並將其注入,即是接下來的重點,這裏涉及到一個很是重要的函數ConstructorResolver#resolvePreparedArguments,該函數的做用是將BeanDefinition中定義的入參轉換爲真是須要的參數(xml中定義的或者註解中定義的),在 BeanDefinitionReader 一文中有過介紹,ref會被封裝爲RuntimeBeanReference存儲、value會被封裝爲TypedStringValue存儲等等,如何將這些封裝好的存儲類型轉爲真正須要的函數參數,即是ConstructorResolver#resolvePreparedArguments函數的做用

Flowchart.resolvePreparedArguments

這裏分紅了三大分支

  • resolveValueIfNecessary

    針對BeanMetadataElement,進行值的轉換,其中又會包含特別細的分支,大體以下

    • RuntimeBeanNameReference

      AbstractBeanFactory#evaluateBeanDefinitionString

      支持Spl表達式解析bean name

    • BeanDefinitionHolder 、BeanDefinition

      BeanDefinitionValueResolver#resolveInnerBean

      與createBean函數的邏輯相似,建立一個inner bean

    • DependencyDescriptor

      AutowireCapableBeanFactory#resolveDependency

      用來處理OptionalBean、LazyBean、AutowireCandidateBean等(詳見下文「註解注入」一節)

    • ManagedArray、ManagedList、ManagedSet、ManagedMap

      BeanDefinitionValueResolver#resolveManagedArray

      BeanDefinitionValueResolver#resolveManagedList

      BeanDefinitionValueResolver#resolveManagedSet

      BeanDefinitionValueResolver#resolveManagedMap

      內部遞歸使用resolveValueIfNecessary方法獲取bean並最終封裝成對應的類型

    • ManagedProperties

      經過BeanDefinitionValueResolver#evaluate(Spel)計算value的值,最終封裝爲Properties

    • TypedStringValue

      經過BeanDefinitionValueResolver#evaluate(Spel)計算value的值

對於這部份內容,Spring在接下來的發展中可能還會不斷地擴充

  • String

    AbstractBeanFactory#evaluateBeanDefinitionString

    與resolveValueIfNecessary中的RuntimeBeanNameReference一致,支持Spl表達式解析表達式

  • 其餘

    • InjectionPoint

      使用ThreadLocal提供當前bean被注入到的注入點,能夠參考 https://www.baeldung.com/what...

      @Bean
      @Scope("prototype")
      public Logger logger(InjectionPoint injectionPoint) {
          // return MyComponent.class
          return Logger.getLogger(injectionPoint.getMethodParameter().getContainingClass());
      }
      
      @Component
      public class MyComponent {
        @Autowired
        private Logger logger;
      }
    • 其餘

      與resolveValueIfNecessary中的DependencyDescriptor一致,用來處理OptionalBean、LazyBean等

須要留意的是,上述在進行依賴注入的過程當中,都會調用 DefaultSingletonBeanRegistry#registerDependentBean方法,將各bean之間的依賴關係保存起來,如同前文在介紹加載depends-on時一致,bean之間的依賴關係會分別存放在 dependentBeanMapdependenciesForBeanMap之中(下文在介紹屬性注入的時候亦是如此),這對於bean銷燬順序的理解起着相當重要的做用

在返回以前還有convertIfNecessary的方法調用,該函數是將上述解析獲得的值轉換爲函數參數真正的類型

爲什麼要轉換?其實上述過程拿到的值並不是真正須要的值,如

public class MyComponent {
  private Resource initSql;
}
<bean id="myComponent" class="com.manerfan.MyComponent">
  <property name="initSql" value="classpath:/init/sql/init.sql"></property>
</bean>

或者

public class MyComponent {
  @Value("${init.sql}")
  // or @Value("classpath:/init/sql/init.sql")
  private Resource initSql;
}
init.sql=classpath:/init/sql/init.sql

不論哪一種形式,在convertIfNecessary以前解析到的值都是字符串 "classpath:/init/sql/init.sql",convertIfNecessary的做用即是將上述解析獲得的值轉換爲函數參數真正的類型Resource

convert的邏輯在TypeConverterDelegate#convertIfNecessary,其內部基本的邏輯爲

  1. 找到合適的PropertyEditor (propertyEditorRegistry)
  2. 使用PropertyEditor的setValue/getValue方法進行轉換

url轉換爲ResourcePropertyEditor對應爲 org.springframework.core.io.ResourceEditor,正是使用ResourceEditor將 字符串"classpath:/init/sql/init.sql」轉爲對應的Resource

Q: Spring默認的PropertyEditor有哪些?又是何時註冊的?

3. 有參構造函數

AbstractAutowireCapableBeanFactory#autowireConstructor

ConstructorResolver#autowireConstructor

使用有參構造函數建立bean實例的一個點在於尋找與參數相對應的構造函數(可能定義了多個構造函數),而對於參數的解析和轉換(參數的依賴注入)則與使用factory method同樣,調用ConstructorResolver#resolvePreparedArguments函數進行處理,這裏再也不重複描述

在拿到真實的入參及對應的構造函數後,下一步即是使用構造函數來建立bean實例,但事情貌似也並無那麼簡單

實例化的過程在ConstructorResolver#instantiate,內部並無統一利用反射技術直接使用構造函數建立,而是分爲兩種狀況

Flowchart.instantiate

一種,沒有設置override-method時,直接使用構造函數建立

一種,在設置了override-method時,使用cglib技術構造代理類,並代理override方法

以上,Spring默認的實例化策略爲CglibSubclassingInstantiationStrategy

4. 無參構造函數

AbstractAutowireCapableBeanFactory#instantiateBean

無參構造函數建立bean實例的過程與有參構造函數建立過程徹底一致,只是少了參數的依賴注入,使用默認無參構造函數進行實例化

BeanDefinition後處理

AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors

在屬性注入以前提供一次機會來對BeanDefinition進行處理,內部執行全部註冊MergedBeanDefinitionPostProcessorpostProcessMergedBeanDefinition方法

在閱讀源碼時注意到一個MergedBeanDefinitionPostProcessor的實現類 AutowiredAnnotationBeanPostProcessor,深刻到實現內部AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata,其實現了兩個註解類的解析 @Value@Autowired ,找到註解修飾的Filed或者Method並緩存,具體的邏輯會在屬性注入一節中詳細介紹

Q: Spring註冊了哪些MergedBeanDefinitionPostProcessor?它們都是作什麼用的?又是何時註冊的?

提早暴露實體

DefaultSingletonBeanRegistry#addSingletonFactory -> AbstractAutowireCapableBeanFactory#getEarlyBeanReference

還記得上文介紹的「嘗試獲取單例」(AbstractBeanFactory.getSingleton)麼?爲了解決循環依賴會將singleton-bean提早暴露出來,暴露的邏輯會封裝爲ObjectFactory(AbstractAutowireCapableBeanFactory#getEarlyBeanReference實現)緩存在DefaultSingletonBeanRegistry.singletonFactories中,在getBean的邏輯getSingleton中會執行ObjectFactory的邏輯將singleton提早暴露

此時暴露的singleton-bean僅完成了bean的實例化,屬性注入、初始化等邏輯均暫未執行

屬性注入

AbstractAutowireCapableBeanFactory#populateBean

在「建立bean實體」小節中介紹了factory method方式及有參構造函數方式的參數注入邏輯,除此以外還有一種注入即是屬性注入

Flowchart.populateBean

流程圖中兩次出現了InstantiationAwareBeanPostProcessor,還記得在「Bean建立過程」小結中介紹的InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation麼?若是該步驟生成了「代理」bean instance,則會有一個短路操做,直接返回該bean instance而再也不執行後續的doCreate

InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation一樣是一個短路操做,若是有任意一個InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation方法返回false,則會跳出屬性注入的邏輯,官方對此的解釋以下

Give any InstantiationAwareBeanPostProcessors the opportunity to modify the state of the bean before properties are set. This can be used, for example, to support styles of field injection.

autowireByNameautowireByType方法做爲「候補」補充BeanDefinition的propertyValues

Fill in any missing property values with references to other beans.

PropertyValue中記錄了須要注入的屬性信息及須要注入的屬性值,那BeanDefinition的propertyValues都來自哪裏?xml中的bean配置、自定義的BeanDefinition等

public class MyService {
    /**
     * string
     */
    private String name;

    /**
     * resource
     */
    private Resource res;

    /**
     * bean ref
     */
    private MyComponent myComponent;
}

xml中定義PropertyValue

<bean id="myMyService" class="com.manerfan.MyService">
  <property name="name" value="SpringDemoApp"></property>
  <property name="res" value="classpath:/init/init.sql"></property>
  <property name="myComponent" ref="myComponent"></property>
</bean>

BeanDefinition中直接定義PropertyValue

// 構建BeanDefinition
BeanDefinition bd = BeanDefinitionBuilder
    .genericBeanDefinition(MyService.class)
    .addPropertyValue("name", "${spring.application.name}")
    .addPropertyValue("res", "classpath:/init/init.sql")
    .addPropertyReference("myComponent", "myComponent")
    .getBeanDefinition();
// 註冊BeanDefinition
beanFactory.registerBeanDefinition("myService", bd);

全部的ProperValue均會在AbstractAutowireCapableBeanFactory#applyPropertyValues中進行依賴的解析、轉換並設置到bean實例對應的屬性中,詳細的邏輯下文介紹

註解注入

除此以外經過註解修飾的屬性(方法)是如何注入的?

public class MyService {
    /**
     * string
     */
    @Value("${spring.application.name}")
    private String name;

    /**
     * resource
     */
    @Value("classpath:/init/init.sql")
    private Resource res;

    /**
     * bean ref by parameter
     */
    @Autowired
    @Qualifier("myComponent1")
    // or @Resource("myComponent1")
    // or @Inject
    private MyComponent myComponent1;
    
    private MyComponent myComponent2;
    
    /**
     * bean ref by setter method
     */
    @Autowired
    public void setMyComponet2(@Qualifier("myComponent1") MyComponet component) {
        this.myComponent2 = component
    }
}
各註解的使用能夠參考 https://www.baeldung.com/spri...

AbstractAutowireCapableBeanFactory#applyPropertyValues以前發現還有一個動做InstantiationAwareBeanPostProcessor#postProcessProperties(是的InstantiationAwareBeanPostProcessor又出現了),在此有兩個實現引發了個人注意AutowiredAnnotationBeanPostProcessor#postProcessPropertiesCommonAnnotationBeanPostProcessor#postProcessProperties,咱們來對比兩個實現的內部邏輯

// AutowiredAnnotationBeanPostProcessor#postProcessProperties
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        metadata.inject(bean, beanName, pvs);
    } catch (Throwable ex) { /* handle exception */ }
    return pvs;
}
// CommonAnnotationBeanPostProcessor#postProcessProperties
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
    try {
        metadata.inject(bean, beanName, pvs);
    } catch (Throwable ex) { /* handle exception */ }
    return pvs;
}

Flowchart.postProcessProperties

從代碼及流程圖能夠看出,兩種實現的差別僅在InjectionMetadata的查找邏輯,一個個來

AutowiredAnnotation

AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata的核心邏輯能夠追蹤到AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata

Flowchart.buildAutowiringMetadata

CommonAnnotation

CommonAnnotationBeanPostProcessor#findResourceMetadata的核心邏輯能夠追蹤到CommonAnnotationBeanPostProcessor.buildResourceMetadata

Flowchart.buildResourceMetadata

InjectionElement

以上,對於不一樣的註解不一樣的方式(屬性/方法),會被封裝爲不一樣的InjectionElement,並最終將全部的InjectionElement封裝在InjectionMetadata

在找到InjectionElement以後,下一步即是依賴的解析和注入了(InjectionMetadata#inject

這裏的邏輯無非就是遍歷內部全部的InjectionElement並執行InjectionElement.inject,上面已經介紹,對於不一樣的註解不一樣的方式(屬性/方法),會被封裝爲不一樣的InjectionElement,那不一樣的InjectionElement也會有不一樣的inject邏輯,至此咱們大體能夠理出一個註解注入的大框架

Flowchart.injectByAnnotation

因此,歸根結底,注入的過程在AutowiredFieldElementAutowiredMethodElementResourceElement、等InjectionElement內部,在繼續進行以前有必要了解一下DefaultListableBeanFactory#resolveDependency

還記得上文「建立bean實體」一節中介紹參數的注入時提到的AutowireCapableBeanFactory#resolveDependency麼?該函數正是調用了DefaultListableBeanFactory#resolveDependency,上文並未詳細展開該函數的邏輯實現,其除了處理OptionalBean、及LazyBean以外,咱們比較關心的邏輯在DefaultListableBeanFactory#doResolveDependency

Flowchart.doResolveDependency

該函數處理了@Value@Qualifier@Primary@Order等的邏輯

@Value的解析有兩個過程,1. StringValueResolver解析(${spring.sql.init} -> classpath:/init/init.sql);2. PropertyEditor轉換(classpath:/init/init.sql -> Resouce);

AutowiredFieldElement無非就是使用DefaultListableBeanFactory#doResolveDependency將依賴的bean解析到,並設置到對應的屬性上

AutowiredMethodElement則是使用DefaultListableBeanFactory#doResolveDependency將參數對應依賴的bean解析到,並執行對應的方法

Q: 咱們是否能夠自定義註解( InstantiationAwareBeanPostProcessor),來實現相似 @Value@Autowired 的功能?

屬性注入

AbstractAutowireCapableBeanFactory#applyPropertyValues

還記得在一開始提到的BeanDefinition中的propertyValues麼?(xml中的bean配置、自定義的BeanDefinition,也有可能來自InstantiationAwareBeanPostProcessor#postProcessProperties),至此這一部分的屬性還未注入依賴

PropertyValue中記錄了須要注入的屬性,已經依賴的類型(StringRuntimeBeanReference、等),根據不一樣的類型解析依賴的bean並設置到對應的屬性上(此過程與DefaultListableBeanFactory#doResolveDependency極其類似,再也不贅述)

初始化

AbstractAutowireCapableBeanFactory#initializeBean

以上,完成了bean實例的建立和屬性注入,以後還有一些初始化的方法,好比各類AwaresetXxx是如何調用的、@PostConstruct是怎麼調用的?

Flowchart.initializeBean

Q: Aware類有不少,除了上圖中的三種以外,其餘的Aware是何時調用的?

Q: @PreDestroy是如何調用的?destroy-method是什麼時候執行的?

Q: AbstractAdvisingBeanPostProcessor都作了什麼?是如何處理AOP代理的?

註冊Disposable

AbstractBeanFactory#registerDisposableBeanIfNecessary

至此,終於完成了bean實例的建立、屬性注入以及以後的初始化,此後即可以開始使用了

在使用Spring的過程當中常常還會碰到設置銷燬邏輯的狀況,如數據庫鏈接池、線程池等等,在Spring銷燬bean的時候還須要作一些處理,相似於C++中的析構

在bean的建立邏輯中,最後一個步驟則是註冊bean的銷燬邏輯(DisposableBean)

銷燬邏輯的註冊有幾個條件

  1. 非prototype(singleton或者註冊的scope)
  2. 非NullBean
  3. 指定了destroy-method(如xml中指定或者BeanDefinition中直接設置)或者存在@PreDestroy註解的方法(CommonAnnotationBeanPostProcessor.requiresDestruction
if (!mbd.isPrototype() && requiresDestruction(bean, mbd))

知足以上條件的bean會被封裝爲DisposableBeanAdapter,並註冊在DefaultSingletonBeanRegistry.disposableBeans中(詳見附錄#中間態說明)

Q: 理解了bean的銷燬註冊邏輯,那bean的銷燬時什麼時候觸發以及如何執行的?

嘗試類型轉換

以上,完成了bean的建立、屬性的注入、dispose邏輯的註冊,但得到的bean類型與實際須要的類型可能依然不相符,在最終交付bean以前(getBean)還須要進行一次類型轉換,上文反覆提到過PropertyEditor,此處不例外,使用的既是PropertyEditor進行的類型轉換,具體的邏輯再也不贅述,再將bean轉換爲真正須要的類型後,便完成了整個getBean的使命

Bean銷燬過程

瞭解了bean的完成建立過程後,那bean是如何銷燬的呢?

bean的建立過程始於DefaultListableBeanFactory.getBean,銷燬過程則終於ConfigurableApplicationContext#close,跟蹤下去,具體的邏輯在DefaultSingletonBeanRegistry#destroySingletons

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
public void destroySingletons() {
    synchronized (this.singletonObjects) {
        this.singletonsCurrentlyInDestruction = true;
    }

    String[] disposableBeanNames;
    synchronized (this.disposableBeans) {
        disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
    }
    for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
    // 遍歷註冊的DisposableBean
        destroySingleton(disposableBeanNames[i]);
    }

  // 清理各類緩存
    this.containedBeanMap.clear();
    this.dependentBeanMap.clear();
    this.dependenciesForBeanMap.clear();
    clearSingletonCache();
}

在介紹bean建立的時候提到過兩個概念

  1. DefaultSingletonBeanRegistry.disposableBeans

    須要註冊銷燬邏輯的bean會被封裝爲DisposableBeanAdapter並緩存在此處

  2. DefaultSingletonBeanRegistry.dependentBeanMap

    對於存在依賴注入關係的bean,會將bean的依賴關係緩存在此處(dependentBeanMap: 哪些bean依賴了我; dependenciesForBeanMap: 我依賴了哪些bean)

Flowchart.destroySingleton

從上圖中能夠看出,bean的銷燬順序與建立順序正好相反,若是有 beanA --dependsOn--> beanB --> beanC ,建立(getBean)時必定是beanC -> beanB -> beanA,銷燬時必定是 beanA -> beanB -> beanC,以此避免由於依賴關係形成的一些異常狀況

循環依賴

在介紹Bean建立的時候提到過earlySingletonObject,爲了解決循環依賴的問題,在實例化完後屬性注入以前會提早將當前的bean實體暴露出來,以防止在屬性注入過程當中所注入的bean又依賴當前的bean形成的相似「死鎖」的狀態,但即使有這樣的邏輯仍是要注意幾點

Beans.CircularReferences

顯示設置dependsOn的循環依賴

@DependsOn("beanB")
@Component
public class BeanA {}

@DependsOn("beanC")
@Component
public class BeanB {}

@DependsOn("beanA")
@Component
public class BeanC {}

dependsOn的依賴,在bean的建立以前便會處理

Spring在實例化以上bean時,在建立BeanA以前會觸發建立BeanB,建立BeanB以前會觸發建立BeanC,而建立BeanC以前又會觸發建立BeanA,由此引起一個無解的循環依賴

構造函數循環依賴

@Component
public class BeanA {
    public BeanA(BeanB beanB) {
    }
}

@Component
public class BeanB {
    public BeanB(BeanC beanC) {
    }
}

@Component
public class BeanC {
    public BeanC(BeanA beanA) {
    }
}

與dependsOn同樣的原理,構造函數參數依賴,一樣在bean的建立以前便會處理,從而引起無解的循環依賴

factory-method依賴

@Bean
public BeanA beanA(BeanB beanB) {
    return new BeanA();
}

@Bean
public BeanB beanB(BeanC beanC) {
    return new BeanB();
}

@Bean
public BeanC beanC(BeanA beanA) {
    return new BeanC();
}

原理與上述相同,再也不贅述

顯示dependsOn、構造函數依賴、factory-method依賴任意混合

@DependsOn("beanB")
@Component
public class BeanA {
}

@Component
public class BeanB {
    public BeanB(BeanC beanC) {
    }
}

@Bean
public BeanC beanC(BeanA beanA) {
    return new BeanC();
}

彷佛咱們找到了必定的規律,只要一個循環依賴中的全部bean,其依賴關係都須要在建立bean實例以前進行解決,此循環依賴則必定無解

打破無解的循環依賴

還以上述三個Bean爲例,先將其中任意一個依賴設置爲屬性依賴(屬性依賴的處理,在bean實例建立完成且暴露earlySingleton以後)

@Component
public class BeanA {
    @Autowired
    private BeanB beanB;
}

@Component
public class BeanB {
    public BeanB(BeanC beanC) {
    }
}

@Bean
public BeanC beanC(BeanA beanA) {
    return new BeanC();
}

@DependsOn("beanB")
@Component
public class BeanA {
}

@Component
public class BeanB {
    private BeanC beanC;

    @Resource
    public void setBeanC(BeanC beanC) {
        this.beanC = beanC;
    }
}

@Bean
public BeanC beanC(BeanA beanA) {
    return new BeanC();
}

等等

爲了不無解的循環依賴,在構成循環依賴的一個環中,只須要保證其中至少一個bean的依賴在該bean建立且暴露earlySingleton以後處理便可

咱們以「bean建立且暴露earlySingleton」爲節點,在此以前處理依賴的有instance supplier parameterfactory method parameterconstructor parameter、等,在此以後處理的依賴有 class propertysetter parameter、等

小結

本文介紹了Spring體系內bean的建立及銷燬過程,在通過萬次的commit後,也造就了Spring必定程度上的複雜度

本文並未全方位的詮釋bean的建立過程,文中遺留了不少疑問點,同時也能發現Spring提供了衆多的擴展點來加強Ioc的能力,讓開發者可以更好的使用/駕馭

下一篇文章,將着重介紹Spring本文中遺留的疑問點及Spring所提供的各類擴展能力

附錄

中間態說明

所在類 屬性 類型 描述
SimpleAliasRegistry aliasMap Map<String, String> Map from alias to canonical name.<br/> 別名alias到原始bean-name的映射
DefaultSingletonBeanRegistry earlySingletonObjects Map<String, Object> Cache of early singleton objects: bean name to bean instance<br/> 緩存提早曝光的singleton-bean-instance,主要用於解決循環依賴,存在於該處的實例還未執行屬性依賴注入及其餘後處理邏輯
singletonFactories Map<String, ObjectFactory> Cache of singleton factories: bean name to ObjectFactory.<br/> 緩存用於生成、獲取earlySingletonObject的工廠方法
singletonObjects Map<String, Object> Cache of singleton objects: bean name to bean instance.<br/> 緩存已經實例化的singleton-bean-instance
dependentBeanMap Map<String, Set<String>> Map between dependent bean names: bean name to Set of dependent bean names.<br/> 若是 beanA -> beanB, beanA -> beanC,key爲beanA,value爲[beanB, beanC]
dependenciesForBeanMap Map<String, Set<String>> Map between depending bean names: bean name to Set of bean names for the bean's dependencies.<br/> 若是 beanB -> beanA, beanC -> beanA,key爲beanA,value爲[beanB, beanC]
singletonsCurrentlyInCreation Set<String> Names of beans that are currently in creation.<br/> 緩存正在建立中的singleton-bean-name
registeredSingletons Set<String> Set of registered singletons, containing the bean names in registration order.<br/> 緩存已經建立的singleton-bean-name
disposableBeans Map<String, Object> Disposable bean instances: bean name to disposable instance.<br/> 緩存註冊的可銷燬bean(bean的銷燬邏輯)
FactoryBeanRegistrySupport factoryBeanObjectCache Map<String, Object> Cache of singleton objects created by FactoryBeans: FactoryBean name to object.<br/> 針對singleton,緩存FactoryBean獲得的真正的singleton-bean-instance

訂閱號

相關文章
相關標籤/搜索