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

神秘傑克發表於2022-06-06

bean 的載入(二)

之前文章主要對 getBean 方法進行簡單的介紹,和 FactoryBean 的作用,以及是如何從快取中獲取 bean。本文繼續講解 bean 的載入流程。

從 bean 的例項中獲取物件

在 getBean 方法裡,getObjectForBeanInstance()是個常用的方法,無論是從快取中獲取 bean 還是根據不同 scope 策略來載入 bean。總而言之,我們在獲取到 bean 例項後第一步就是呼叫這個方法來檢測正確性,其實就是檢測當前 bean 是否為 FactoryBean 型別的 bean,如果是則呼叫 FactoryBean 例項的 getObject()作為返回值。

需要注意的是無論是快取中獲取到的 bean 還是透過 scope 策略載入的 bean 都是原始的 bean 狀態,我們需要的是工廠 bean 中定義 factory-method 方法中返回的 bean,而 getObjectForBeanInstance 方法就是完成這個工作的。

protected Object getObjectForBeanInstance(
      Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

   //如果指定的name是工廠相關以"&"為字首,並且beanInstance又不是FactoryBean則校驗不透過
   if (BeanFactoryUtils.isFactoryDereference(name)) {
      if (beanInstance instanceof NullBean) {
         return beanInstance;
      }
      if (!(beanInstance instanceof FactoryBean)) {
         throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
      }
      if (mbd != null) {
         mbd.isFactoryBean = true;
      }
      return beanInstance;
   }

   //現在我們有了這個bean例項,這個例項可能是FactoryBean,也可能是正常的bean
   //如果是FactoryBean的話,我們使用它建立例項,但如果是使用者想要直接獲取工廠例項而不是工廠的getObject方法則需要在BeanName前加上"&"
   if (!(beanInstance instanceof FactoryBean)) {
      return beanInstance;
   }

   //載入FactoryBean
   Object object = null;
   if (mbd != null) {
      mbd.isFactoryBean = true;
   }
   else {
      //嘗試從快取中載入bean
      object = getCachedObjectForFactoryBean(beanName);
   }
   if (object == null) {
      // 執行到這裡表明beanInstance一定是一個FactoryBean
      FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
      // containsBeanDefinition檢測BeanDefinitionMap中也就是在所有一鍵載入的類中檢測是否定義beanName
      if (mbd == null && containsBeanDefinition(beanName)) {
         //將GenericBeanDefinition轉換為RootBeanDefinition,如果指定BeanName是子bean的話同時會合並父類的屬性
         mbd = getMergedLocalBeanDefinition(beanName);
      }
      //是否是使用者定義的而不是應用程式本身定義的
      boolean synthetic = (mbd != null && mbd.isSynthetic());
      object = getObjectFromFactoryBean(factory, beanName, !synthetic);
   }
   return object;
}

我們先看一下 getObjectForBeanInstance 主要做了什麼:

  • 對 FactoryBean 正確性進行驗證
  • 對非 FactoryBean 不作任何處理
  • 對 bean 進行轉換
  • 將從 Factory 中解析 bean 的工作委託給了getObjectFromFactoryBean

該段程式碼大多都是輔助程式碼,真正的核心程式碼委託給了getObjectFromFactoryBean

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
   if (factory.isSingleton() && containsSingleton(beanName)) {
      synchronized (getSingletonMutex()) {
         Object object = this.factoryBeanObjectCache.get(beanName);
         if (object == null) {
               //重點方法 doGetObjectFromFactoryBean
            object = doGetObjectFromFactoryBean(factory, beanName);
            //...省略
   }
   else {
      //重點方法 doGetObjectFromFactoryBean
      Object object = doGetObjectFromFactoryBean(factory, beanName);
      if (shouldPostProcess) {
         try {
            object = postProcessObjectFromFactoryBean(object, beanName);
         }
         catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
         }
      }
      return object;
   }
}

這段程式碼就是返回的 bean 如果是單例的則需要保證全域性唯一,也正因為是單例的所以不必重複建立,可以用快取來提高效能。

我們進入doGetObjectFromFactoryBean方法中。

private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
      throws BeanCreationException {

   Object object;
   try {
      //是否需要許可權校驗
      if (System.getSecurityManager() != null) {
         AccessControlContext acc = getAccessControlContext();
         try {
            object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
         }
         catch (PrivilegedActionException pae) {
            throw pae.getException();
         }
      }
      else {
         //直接呼叫getObject方法(重點)
         object = factory.getObject();
      }
   }
   catch (FactoryBeanNotInitializedException ex) {
      throw new BeanCurrentlyInCreationException(beanName, ex.toString());
   }
   catch (Throwable ex) {
      throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
   }

   // Do not accept a null value for a FactoryBean that's not fully
   // initialized yet: Many FactoryBeans just return null then.
   if (object == null) {
      if (isSingletonCurrentlyInCreation(beanName)) {
         throw new BeanCurrentlyInCreationException(
               beanName, "FactoryBean which is currently in creation returned null from getObject");
      }
      object = new NullBean();
   }
   return object;
}

在該方法中,我們終於看到了想要看到的程式碼,也就是object = factory.getObject()。之前我們已經講過了 FactoryBean 的呼叫方法,如果 bean 是 FactoryBean 型別,則當提取 bean 時提取的並不是 factoryBean 而是 factoryBean 的 getObject 方法的返回值。

獲取單例

之前我們說過如果快取中不存在已經載入的 bean 則需要重頭開始 bean 的載入,在 Spring 中使用 getSingleton 的過載方法實現了 bean 的載入過程。

getBean 方法:
// 例項化依賴的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);
}

進入 getSingleton 方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(beanName, "Bean name must not be null");
   //加鎖
   synchronized (this.singletonObjects) {
      //首先檢查對應的bean是否已經載入過,
      Object singletonObject = this.singletonObjects.get(beanName);
      //如果為空則需要進行singleton的bean初始化
      if (singletonObject == null) {
         if (this.singletonsCurrentlyInDestruction) {
            throw new BeanCreationNotAllowedException(beanName,
                  "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                  "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
         }
         if (logger.isDebugEnabled()) {
            logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
         }
         //程式碼(1)
         beforeSingletonCreation(beanName);
         boolean newSingleton = false;
         boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
         if (recordSuppressedExceptions) {
            this.suppressedExceptions = new LinkedHashSet<>();
         }
         try {
            // 透過回撥方式獲取bean例項。
            singletonObject = singletonFactory.getObject();
            newSingleton = true;
         }
         catch (IllegalStateException ex) {
            // Has the singleton object implicitly appeared in the meantime ->
            // if yes, proceed with it since the exception indicates that state.
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               throw ex;
            }
         }
         catch (BeanCreationException ex) {
            if (recordSuppressedExceptions) {
               for (Exception suppressedException : this.suppressedExceptions) {
                  ex.addRelatedCause(suppressedException);
               }
            }
            throw ex;
         }
         finally {
            if (recordSuppressedExceptions) {
               this.suppressedExceptions = null;
            }
            //程式碼(2)
            afterSingletonCreation(beanName);
         }
         if (newSingleton) {
            //加入快取 程式碼(3)
            addSingleton(beanName, singletonObject);
         }
      }
      return singletonObject;
   }
}

上述程式碼使用了回撥方法,在單例建立的前後做了些準備及處理操作,而真正獲取單例 bean 的方法是在 ObjectFactory 型別的例項 singletonFactory 中。我們先看一下 getSingleton 方法主要做了什麼:

  1. 檢查快取是否已經載入過
  2. 若沒有載入,則記錄 beanName 為正在載入狀態
  3. 載入單例前記錄載入狀態,程式碼(1)
protected void beforeSingletonCreation(String beanName) {
   if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
   }
}

這個方法主要做的就是記錄載入狀態,this.singletonsCurrentlyInCreation.add(beanName)將當前正要建立的 bean 記錄在快取中,這樣就可以對迴圈依賴進行檢測。

  1. 獲取 bean 例項
  2. 呼叫載入單例後的處理方法,程式碼(2)
protected void afterSingletonCreation(String beanName) {
   if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
      throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
   }
}

和上述類似,只是這裡是從快取中移除 bean 的正在載入狀態。

  1. 將 bean 加入快取,並刪除載入 bean 過程中所記錄的各種輔助狀態。
protected void addSingleton(String beanName, Object singletonObject) {
   synchronized (this.singletonObjects) {
      this.singletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
   }
}
  1. 返回處理結果

現在我們已經瞭解了 bean 的邏輯架構,但是 bean 的載入邏輯是在傳入 ObjectFactory 型別的引數 singletonFactory 中定義的。

//建立Bean例項物件,並且註冊給所依賴的物件
sharedInstance = getSingleton(beanName, () -> {
   try {
      return createBean(beanName, mbd, args);
   }
   catch (BeansException ex) {
      //從單例快取中刪除bean例項
      //因為單例模式下為了解決迴圈依賴,可能它已經存在了,所以將其銷燬
      destroySingleton(beanName);
      throw ex;
   }
});

我們進入 createBean 方法中繼續檢視。

準備建立 bean

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {

   if (logger.isTraceEnabled()) {
      logger.trace("Creating instance of bean '" + beanName + "'");
   }
   RootBeanDefinition mbdToUse = mbd;

   // Make sure bean class is actually resolved at this point, and
   // clone the bean definition in case of a dynamically resolved Class
   // which cannot be stored in the shared merged bean definition.
   // 鎖定class,根據設定的class屬性或者根據className來解析Class
   Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
   if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
      mbdToUse = new RootBeanDefinition(mbd);
      mbdToUse.setBeanClass(resolvedClass);
   }

   // Prepare method overrides.
   //驗證及準備覆蓋的方法
   try {
      mbdToUse.prepareMethodOverrides();
   }
   catch (BeanDefinitionValidationException ex) {
      throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
            beanName, "Validation of method overrides failed", ex);
   }

   try {
      // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
      //給BeanPostProcessors一個機會來返回代理用於替代真正的例項
      Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
      if (bean != null) {
         return bean;
      }
   }
   catch (Throwable ex) {
      throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
            "BeanPostProcessor before instantiation of bean failed", ex);
   }

   try {
      //程式碼(1)
      Object beanInstance = doCreateBean(beanName, mbdToUse, args);
      if (logger.isTraceEnabled()) {
         logger.trace("Finished creating instance of bean '" + beanName + "'");
      }
      return beanInstance;
   }
   catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
      // A previously detected exception with proper bean creation context already,
      // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
      throw ex;
   }
   catch (Throwable ex) {
      throw new BeanCreationException(
            mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
   }
}
  1. 根據設定的 class 屬性或者根據 className 來解析 Class
  2. 對 override 屬性進行標記及驗證

我們知道在 Spring 的配置中並沒有類似於 override-methdo 之類的配置,那麼該方法的作用是什麼?

我們之前說過 Spring 配置中存在 lookup-method 和 replace-method 的,這兩個配置的載入就是將配置統一存放在 BeanDefinition 中的 methodOverrides 屬性中,而這個函式其實也就是針對這兩個配置的。

  1. 應用初始化前的後處理器,解析指定 bean 是否存在初始化前的短路操作
  2. 建立 bean

我們先看一下 override 屬性標記及驗證的邏輯實現

處理 override 屬性

進入 prepareMethodOverrides 方法:

public void prepareMethodOverrides() throws BeanDefinitionValidationException {
   // Check that lookup methods exist and determine their overloaded status.
   if (hasMethodOverrides()) {
      getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride);
   }
}
protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
   // 獲取對應類中對應方法名的個數
   int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
   if (count == 0) {
      throw new BeanDefinitionValidationException(
            "Invalid method override: no method with name '" + mo.getMethodName() +
            "' on class [" + getBeanClassName() + "]");
   }
   else if (count == 1) {
      // Mark override as not overloaded, to avoid the overhead of arg type checking.
      // 標記MethodOverride暫未被覆蓋,避免引數型別檢查的開銷
      mo.setOverloaded(false);
   }
}

剛才說到 lookup-method 和 replace-method 兩個配置功能是統一存放在 BeanDefinition 中的 methodOverrides 屬性中,這兩個功能實現原理就是在 bean 例項化的時候如果檢測到存在 methodOverrides 屬性,就會動態地為當前 bean 生成代理並使用對應的攔截器對 bean 做增強處理。

需要提到的是,對於方法的匹配來說,如果一個類中有多個過載方法則需要根據引數型別進行匹配。如果類中只有方法只有一個那麼就設定該方法沒有被過載,這樣在後續時候可以直接使用找到的方法,不需要進行方法的引數匹配驗證,而且還可以提前對方法存在性進行驗證,所謂一箭雙鵰。

例項化的前置處理

在呼叫 doCreateBean 之前,還使用了 resolveBeforeInstantiation(beanName, mbdToUse)方法對 BeanDefinition 中的屬性做些前置處理。

Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
   return bean;
}

該段程式碼的重點就是這個 if 條件,當處理結果不為 null 的時候就會跳過後續 bean 的建立直接返回結果。我們熟知的 AOP 功能就是基於這裡判斷的。

protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
        Object bean = null;
        //如果還沒被解析過,則解析
        if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
            // Make sure bean class is actually resolved at this point.
            if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
                Class<?> targetType = determineTargetType(beanName, mbd);
                if (targetType != null) {
                    //程式碼(1)
                    bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
                    if (bean != null) {
                        //程式碼(2)
                        bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
                    }
                }
            }
            mbd.beforeInstantiationResolved = (bean != null);
        }
        return bean;
}

該方法中的重點就是這兩個方法:applyBeanPostProcessorsBeforeInstantiation、applyBeanPostProcessorsAfterInitialization。這兩個方法實現都很簡單,就是對後處理器中所有的 InstantiationAwareBeanPostProcessor 型別的後處理器進行 postProcessBeforeInstantiation 方法和 BeanPostProcessor 的 postProcessAfterInitialization 方法的呼叫。

1. 例項化前的後處理器應用

bean 的例項化前呼叫,也就是將 AbstractBeanDefinition 轉換為 BeanWrapper 前的處理。給子類一個修改 BeanDefinition 的機會,也就是呼叫這個方法後 bean 就有可能產生變化,有可能是經過處理的代理 bean,也可能是 cglib 生成的。後續會詳細介紹,現在只需要明白在 bean 例項化前會呼叫後處理器的方法進行處理。

protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
   for (BeanPostProcessor bp : getBeanPostProcessors()) {
      if (bp instanceof InstantiationAwareBeanPostProcessor) {
         InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
         Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
         if (result != null) {
            return result;
         }
      }
   }
   return null;
}

2. 例項化後的後處理器應用

Spring 的規則是在 bean 初始化後儘可能保證將註冊的後處理器 postProcessAfterInitialization 方法應用到 bean 中,如果返回的 bean 不為空,則不需要再經歷 bean 的建立過程。

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
      throws BeansException {

   Object result = existingBean;
   for (BeanPostProcessor processor : getBeanPostProcessors()) {
      Object current = processor.postProcessAfterInitialization(result, beanName);
      if (current == null) {
         return result;
      }
      result = current;
   }
   return result;
}

相關文章