Spring原始碼之Bean的載入(一)

神祕傑克發表於2022-05-30

bean 的載入

之前文章主要分析了對 XML 配置檔案的解析,接下來就是對 bean 的載入進行分析,同樣開始用最開始的程式碼為入口。

入口程式碼 getBean

public void testSimpleLoad(){
   final BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
   final MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean");
   assertEquals("testStr",myTestBean.getTestStr());
}

從這裡我們快速先大致瞭解一下是如何實現的。

從 BeanFactory 介面中我們選擇對應實現類為 AbstractBeanFactory。

Object getBean(String name) throws BeansException;
@Override
public Object getBean(String name) throws BeansException {
   return doGetBean(name, null, null, false);
}
    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
        //通過三種形式獲取beanName, 一個是原始的beanName,一個是加了&的,一個是別名
        //返回容器中真是的beanName
        final String beanName = transformedBeanName(name);
        Object bean;

        /**
         * 檢查快取中或例項工廠中是否存在對應例項
         * 因為在建立單例bean的時候會存在依賴注入的情況,Spring為了避免迴圈依賴,建立bean的原則是不等bean建立完成就會建立bean的ObjectFactory提早曝光
         * 也就是將ObjectFactory放入到了快取中,一旦下個bean建立時候需要依賴上一個bean則直接使用ObjectFactory
         */
        // Eagerly check singleton cache for manually registered singletons.
        //嘗試從快取中獲取或者從singleFactories中的ObjectFactory中獲取
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            if (logger.isTraceEnabled()) {
                //如果Bean還在建立中,則說明是迴圈引用
                if (isSingletonCurrentlyInCreation(beanName)) {
                    logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                            "' that is not fully initialized yet - a consequence of a circular reference");
                }
                else {
                    logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
                }
            }
            //如果是普通bean,直接返回,如果是FactoryBean,則返回它的getObject
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }

        else {
            //只有在單例情況下Spring才會嘗試解決迴圈依賴,在原型模式下如果存在A->B->A的話就會丟擲異常
            if (isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }
            //檢查工廠中是否存在bean定義
            BeanFactory parentBeanFactory = getParentBeanFactory();
            //如果beanDefinitionMap中不包括當前beanName則會嘗試從parentBeanFactory中檢測
            if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                //主要針對FactoryBean,將Bean的&重新加上
                //將轉換過後的BeanName恢復回原先的樣子
                String nameToLookup = originalBeanName(name);
                if (parentBeanFactory instanceof AbstractBeanFactory) {
                    //遞迴到BeanFactory中尋找
                    return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                            nameToLookup, requiredType, args, typeCheckOnly);
                }
                else if (args != null) {
                    //如果有引數,則委派父類容器根據指定名稱和引數查詢
                    return (T) parentBeanFactory.getBean(nameToLookup, args);
                }
                else if (requiredType != null) {
                    //委派父級容器根據指定名稱和型別查詢
                    return parentBeanFactory.getBean(nameToLookup, requiredType);
                }
                else {
                    //委派父級容器根據指定名稱查詢
                    return (T) parentBeanFactory.getBean(nameToLookup);
                }
            }
            //如果不是僅僅做型別檢查則是建立bean,進行記錄
            if (!typeCheckOnly) {
                markBeanAsCreated(beanName);
            }

            try {
                //將儲存XML配置檔案的GenericBeanDefinition轉換為RootBeanDefinition,如果指定的BeanName是子bean的話還會合並父類的相同屬性
                final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                //對合並的BeanDefinition做驗證,主要看屬性是否為abstract的
                checkMergedBeanDefinition(mbd, beanName, args);

                String[] dependsOn = mbd.getDependsOn();
                //如果存在依賴則需要遞迴例項化依賴的bean
                if (dependsOn != null) {
                    for (String dep : dependsOn) {
                        if (isDependent(beanName, dep)) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                        }
                        //快取依賴呼叫
                        registerDependentBean(dep, beanName);
                        try {
                            //遞迴呼叫getBean方法,註冊Bean之間的依賴(如C需要晚於B初始化,而B需要晚於A初始化)
                            //初始化依賴的bean
                            getBean(dep);
                        }
                        catch (NoSuchBeanDefinitionException ex) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                        }
                    }
                }
                // 例項化依賴的bean後就可以例項化mbd本身了
                // 如果BeanDefinition為單例
                if (mbd.isSingleton()) {
                    //建立Bean例項物件,並且註冊給所依賴的物件
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            //從單例快取中刪除bean例項
                            //因為單例模式下為了解決迴圈依賴,可能它已經存在了,所以將其銷燬
                            destroySingleton(beanName);
                            throw ex;
                        }
                    });
                    //如果是普通bean,直接返回,如果是FactoryBean,則返回它的getObject
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
                // 如果BeanDefinition為prototype
                else if (mbd.isPrototype()) {
                    // 每次建立一個新的物件
                    Object prototypeInstance = null;
                    try {
                        //註冊當前建立的prototype物件為正在建立中
                        beforePrototypeCreation(beanName);
                        //建立原型物件例項
                        prototypeInstance = createBean(beanName, mbd, args);
                    }
                    finally {
                        //將先前註冊的正在建立中的Bean資訊給抹除掉
                        afterPrototypeCreation(beanName);
                    }
                    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                }
                //如果不是原型模式和單例模式則可能是: request、session、application等生命週期
                else {
                    String scopeName = mbd.getScope();
                    final Scope scope = this.scopes.get(scopeName);
                    //Bean定義資源中沒有配置生命週期範圍,則Bean定義不合法
                    if (scope == null) {
                        throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                    }
                    try {
                        //如果bean的scope不是singleton和prototype,則呼叫scope.get()來選擇合適的載入策略
                        Object scopedInstance = scope.get(beanName, () -> {
                            //註冊當前建立的prototype物件為正在建立中
                            beforePrototypeCreation(beanName);
                            try {
                                //建立bean
                                return createBean(beanName, mbd, args);
                            }
                            finally {
                                //將先前註冊的正在建立中的Bean資訊給抹除掉
                                afterPrototypeCreation(beanName);
                            }
                        });
                        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                    }
                    catch (IllegalStateException ex) {
                        throw new BeanCreationException(beanName,
                                "Scope '" + scopeName + "' is not active for the current thread; consider " +
                                "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                ex);
                    }
                }
            }
            catch (BeansException ex) {
                cleanupAfterBeanCreationFailure(beanName);
                throw ex;
            }
        }

        // Check if required type matches the type of the actual bean instance.
        //檢查需要的型別是否符合bean的實際型別
        if (requiredType != null && !requiredType.isInstance(bean)) {
            try {
                T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
                if (convertedBean == null) {
                    throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
                }
                return convertedBean;
            }
            catch (TypeMismatchException ex) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Failed to convert bean '" + name + "' to required type '" +
                            ClassUtils.getQualifiedName(requiredType) + "'", ex);
                }
                throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
            }
        }
        //返回bean例項
        return (T) bean;
}

可以看到該方法程式碼量很多,我們先大概整理一下流程:

1.轉換對應 beanName

轉換對應 beanName 的意思就是,這裡我們傳入的 beanName 可能是別名也可能是 FactoryBean,所以要進行解析。

包括:

  • 去除 FactoryBean 的修飾符,也就是如果 name=&jack,那麼會去除&而使 name=jack
  • 取指定 alias 所表示的最終 beanName,如果別名 A 指向名稱 B 的 bean,則返回 B 的 bean;如果別名 A 指向別名 B,別名 B 又指向別名 C 則返回 C 的 bean

2.嘗試從快取中載入單例

單例就是在 Spring 的同一容器中只會被建立一次,後續獲取則無需再次建立,直接從單例快取中獲取。但這裡也是嘗試載入 bean,首先嚐試從快取中載入,如果載入不成功則嘗試從 singletonFactories 中載入因為在建立單例bean的時候可能會存在依賴注入的情況,Spring在建立依賴的時候為了避免迴圈依賴,建立bean的原則是不等bean建立完成就會將建立bean的ObjectFactory提前曝光加入到快取中,一旦下一個bean建立時候需要依賴上一個bean則直接使用ObjectFactory

3.bean 的例項化

如果從快取中得到的是未例項化的 bean,則需要進行例項化操作,需要注意:快取中記錄的是最原始的 bean 狀態,不一定是我們想要 bean。我們需要的是工廠 bean 中定義的 factory-method 方法中返回的 bean,而getObjectForBeanInstance方法就是完成這個工作的。

4.原型模式的依賴檢查

只有單例的情況下才會嘗試解決迴圈依賴,如果存在 A 中有 B 屬性,B 中有 A 屬性,當依賴注入時候就會產生 A 還沒建立完的時候因為對於 B 的建立再次返回建立 A,造成迴圈依賴。也就是isPrototypeCurrentlyInCreation(beanName) 為 true。

5.檢測 parentBeanFactory

BeanFactory parentBeanFactory = getParentBeanFactory();

如果從快取中沒有獲取到,就從父類工廠中去載入。

下一行去進行判斷,如果 parentBeanFactory 為空一切都是浮雲,隨後去檢測如果當前載入的 XML 配置中不包含 beanName 所對應的配置,就只能到 parentBeanFactory 中嘗試,然後再去遞迴呼叫 getBean 方法。

if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {

6.將儲存 XML 配置檔案的 GenericBeanDefinition 轉換為 RootBeanDefinition

之前說過從 XML 中讀取到的 bean 資訊是存在 GenericBeanDefinition 中的,但是後續的 bean 處理都是針對 RootBeanDefinition 的,所以要進行一個轉換,同時判斷父類 bean 不為空的話,一併合併父類屬性。

final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

7.尋找依賴

在 bean 初始化中可能會用到某些屬性,而某些屬性可能是動態配置的,並且依賴其他 bean,這時候需要先載入所依賴的 bean。

8. 針對不同 scope 進行 bean 的建立

該階段對不同的 scope 進行 bean 的建立,預設的是 singleton,還有 prototype、request 等。

9.型別轉換

到這裡基本上 bean 的建立就已經結束了,這一步對 bean 進行一個轉換,判斷requiredType != null的話就轉換為實際的型別。經過這些步驟後 bean 的建立就完成了,隨後返回我們需要的 bean。

總結

  1. 通過getSingleton(beanName)方法檢查快取中是否存在已經載入的 bean
  2. 如果條件成立if (sharedInstance != null && args == null),呼叫getObjectForBeanInstance(sharedInstance, name, beanName, null)方法獲取到 bean 例項,但有時存在比如 BeanFactory 的情況並不是直接返回例項本身而是返回指定方法的例項,並跳轉到第 6 步
  3. if (parentBeanFactory != null && !containsBeanDefinition(beanName))如果當前不存在 beanName,呼叫方法 parentBeanFactory.getBean 去父類工廠中去找,父類工廠通常為空
  4. getMergedLocalBeanDefinition(beanName)將儲存 XML 配置檔案的 GenericBeanDefinition 轉換為 RootBeanDefinition,如果指定 beanName 是子 Bean 的話同時合併父類相關屬性 Text
  5. if (mbd.isSingleton()) else if (mbd.isPrototype())根據屬性 bean 的屬性 scope 進行不同的例項化
  6. if (requiredType != null && !requiredType.isInstance(bean))通過指定需求型別不為空進行型別轉換,否則進行強制轉換

瞭解了建立 bean 的整個過程,其中最重要的就是步驟 8,針對不同的 scope 進行建立。在細化各個步驟提供的功能前,我們先了解一下 FactoryBean 的用法。

FactoryBean 的使用

Spring 通過反射機制來利用 bean 的 class 屬性指定實現類來例項化 bean。在某些情況下,例項 bean 比較複雜,如果在<bean>標籤中需要大量的配置資訊並且靈活性受限。

Spring 中提供了org.springframework.beans.factory.FactoryBean的工廠介面,我們可以實現這個介面來定製例項化 bean 的邏輯。

public interface FactoryBean<T> {

   String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

     /**
     * 返回由FactoryBean建立的bean例項,如果isSingleton()返回true,則該例項會放入到Spring容器的單例快取池中
     */
   @Nullable
   T getObject() throws Exception;

  /**
     * 返回FactoryBean建立的bean型別
     */
   @Nullable
   Class<?> getObjectType();

  /**
     * 返回FactoryBean建立的bean例項的作用域是singleton還是prototype
     */
   default boolean isSingleton() {
      return true;
   }

}

當配置檔案中<bean>的 class 屬性配置的實現類為 FactoryBean 時,通過getBean()方法獲取返回到的不是 FactoryBean 本身,而是 FactoryBean#getObject()方法所返回的物件,相當於是代理了 getBean()方法。

如果使用傳統方式配置下面 Car 的<bean>時,每一個屬性則分別對應一個<property>標籤。

/**
 * @author 神祕傑克
 * 公眾號: Java菜鳥程式設計師
 * @date 2022/5/30
 */
public class Car {

   private int maxSpeed;
   private String brand;
   private double price;
   // 省略 get / set
}

如果用 FactoryBean 的方式的話則會更加靈活一點,比如通過逗號分割符的方式一次性為 Car 所有屬性賦值。

/**
 * @author 神祕傑克
 * 公眾號: Java菜鳥程式設計師
 * @date 2022/5/30
 */
public class CarFactoryBean implements FactoryBean<Car> {

   private String carInfo;

   @Override
   public Car getObject() throws Exception {
      Car car = new Car();
      final String[] infos = carInfo.split(",");
      car.setBrand(infos[0]);
      car.setMaxSpeed(Integer.parseInt(infos[1]));
      car.setPrice(Double.parseDouble(infos[2]));
      return car;
   }

   @Override
   public Class<?> getObjectType() {
      return Car.class;
   }

   public String getCarInfo() {
      return carInfo;
   }

   public void setCarInfo(String carInfo) {
      this.carInfo = carInfo;
   }

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

}

有了 CarFactoryBean 後,就可以在配置檔案中使用下面這種自定義配置方式配置 Car Bean 了:

<bean id="car" class="cn.jack.CarFactoryBean">
   <property name="carInfo" value="寶馬,400,20000000"/>
</bean>

當呼叫getBean("car")時,通過反射機制發現了 CarFactoryBean 實現了 FactoryBean 介面,這時候 Spring 容器就呼叫介面方法 CarFactoryBean#getObject()方法返回。如果要獲取 CarFactoryBean 的例項,則需要在 beanName 前面使用“&”作為字首,比如:getBean("&car")

快取中獲取單例 bean

瞭解 FactoryBean 的用法後,我們就可以瞭解 bean 載入的過程了。

我們知道單例在 Spring 的同一個容器內只會被建立一次,後續再獲取 bean 直接從單例快取中獲取,注意這裡也只是嘗試載入

首先嚐試從快取中載入,然後再次嘗試從 singletonFactories 中載入。因為在建立單例 bean 時會存在依賴注入的情況,而在建立依賴的時候為了避免迴圈依賴,Spring 建立 bean 的原則就是不等 bean 建立完成就會將 bean 的 ObjectFactory 提前曝光加入到快取中,一旦下一個 bean 建立時需要依賴上個 bean,則直接使用 ObjectFactory。

public Object getSingleton(String beanName) {
   //引數true表示允許早期依賴
   return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   //檢查快取中是否存在例項
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      //為空則加鎖
      synchronized (this.singletonObjects) {
         //如果此時bean正在載入則不處理
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
            //當某些方法需要提前初始化時候則呼叫addSingleTonFactory方法講對應的ObjectFactory初始化策略存入singletonFactories
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               //呼叫預先設定的getObject方法
               singletonObject = singletonFactory.getObject();
               //記錄到快取中,注意:earlySingletonObjects和singletonFactories是互斥的
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

這個方法首先嚐試從 singletonObjects 中獲取例項,如果獲取不到就從 earlySingletonObjects 中獲取,如果還是獲取不到則從 singletonFactories 裡面獲取 beanName 對應的 ObjectFactory,然後呼叫 ObjectFactory 的 getObject 來建立 bean,並放到 earlySingletonObjects 中,並且從 singletonFactories 中 remove 掉這個 ObjectFactory,後續所有記憶體操作都只為了迴圈依賴檢測時候使用,也就是在 allowEarlyReference 為 true 的情況下使用。

總結

這裡面提到用來儲存 bean 的不同的 map:

  • singletonObjects一級快取,儲存 BeanName 和建立 bean 例項之間的關係,bean name --> bean instance
  • earlySingletonObjects二級快取,儲存 BeanName 和建立 bean 例項之間的關係,與 singletonObjects 不同的是,當一個單例 bean 放入這裡後,那麼當 bean 還在建立過程中時,就可以通過 getBean 方法獲取到了,目的是用來檢測迴圈引用
  • singletonFactories三級快取,儲存 BeanName 和建立 bean 的工廠之間的關係,bean name --> ObjectFactory
  • registeredSingletons:該例項是一個 Set 物件,用來儲存當前所有已經註冊的 bean

相關文章