聊透Spring bean的生命週期

語言: CN / TW / HK

 在對於Spring的所有解讀中,Bean的生命週期都可謂是重中之重,甚至還有人稱Spring就是個管理Bean的容器。Bean的生命週期之所以這麼重要,被反覆提及,是因為Spring的核心能力,比如物件建立(IOC)屬性注入(DI)初始化方法的呼叫代理物件的生成(AOP)等功能的實現,都是在bean的生命週期中完成的。清楚了bean的生命週期,我們才能知道Spring的神奇魔法究竟是什麼,是怎麼一步步賦能,讓原本普通的java物件,最終變成擁有超能力的bean的。

1. bean的生命週期

 Spring的生命週期大致分為:建立 -> 屬性填充 -> 初始化bean -> 使用 -> 銷燬 幾個核心階段。我們先來簡單瞭解一下這些階段所做的事情: - 建立階段主要是建立物件,這裡我們看到,物件的建立權交由Spring管理了,不再是我們手動new了,這也是IOC的概念。 - 屬性填充階段主要是進行依賴的注入,將當前物件依賴的bean物件,從Spring容器中找出來,然後填充到對應的屬性中去。 - 初始化bean階段做的事情相對比較複雜,包括回撥各種Aware介面、回撥各種初始化方法、生成AOP代理物件也在該階段進行,該階段主要是完成初始化回撥,後面我們慢慢分析。 - 使用bean階段,主要是bean建立完成,在程式執行期間,提供服務的階段。 - 銷燬bean階段,主要是容器關閉或停止服務,對bean進行銷燬處理。

 當然,bean的生命週期中還包括其他的流程,比如暴露工廠物件等,只是相對而言都是為其他功能做伏筆和準備的,再講到對應功能時,我們在做詳細分析。

1.1 建立bean

 物件的建立是bean生命週期的第一步,畢竟要先有1才能有0嘛。建立物件的方式有很多,比如 new反射clone等等,Spring是怎麼建立物件的呢?絕大多數情況下,Spring是通過反射來建立物件的,不過如果我們提供了Supplier或者工廠方法,Spring也會直接使用我們提供的建立方式。

 我們從原始碼出發,看一下Spring是如何選擇建立方式的: ```java // 原始碼位於 AbstractAutowireCapableBeanFactory.java protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // 再次解析BeanDefinition的class,確保class已經被解析 Class<?> beanClass = resolveBeanClass(mbd, beanName);

// 1: 如果提供了Supplier,通過Supplier產生物件 Supplier<?> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); }

// 2: 如果有工廠方法,使用工廠方法產生物件 // 在@Configration配置@Bean的方法,也會被解析為FactoryMethod if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } //...省略部分程式碼 // 3: 推斷構造方法 // 3.1 執行後置處理器,獲取候選構造方法 Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); // 3.2 需要自動注入的情況 if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); }

// 3.3 預設使用沒有引數的構造方法 return instantiateBean(beanName, mbd); } `` &emsp;經過我們跟蹤原始碼,發現Spring推斷建立方式還是比較聰明的,具體邏輯是: 1. 先判斷是否提供了Supplier,如果提供,則通過Supplier產生物件。 2. 再判斷是否提供工廠方法,如果提供,則使用工廠方法產生物件。 3. 如果都沒提供,需要進行構造方法的推斷,邏輯為: - 如果僅有一個構造方法,會直接使用該構造方法(如果構造方法有引數,會自動注入依賴引數) - 如果有多個構造引數,會判斷有沒有加了@Autowired註解的構造引數: - 如果沒有,Spring預設選擇無參構造方法; - 如果有,且有@Autowired(required=true)的構造方法,就會選擇該構造方法; - 如果有,但是沒有@Autowired(required=true)的構造方法,Spring會從所有加了@Autowired`的構造方法中,根據構造器引數個數、型別匹配程度等綜合打分,選擇一個匹配引數最多,型別最準確的構造方法。

01_建立bean.png

 關於建立bean時,具體如何選擇構造方法的,本文我們不詳細展開。因為本文主旨在於分析bean的生命週期,我們只需要簡單知道Spring會選擇一個構造方法,然後通過反射創建出物件即可。其實在閱讀Spring原始碼的時候,小夥伴們也一定要學會抓大放小,重點關注核心流程,細枝末節的地方可以先戰術性忽略,後續有需要時再回過頭分析也不遲,千萬不要陷進去,迷失了方向。

 這裡給感興趣的小夥伴附上一張流程圖,感興趣的小夥伴也可以留言,後續我們也可以單獨分析。

02_建立bean時推斷構造方法的流程分析.png

1.2 merged BeanDefinition

 本階段是Spring提供的一個拓展點,通過MergedBeanDefinitionPostProcessor型別的後置處理器,可以對bean對應的BeanDefinition進行修改。Spring自身也充分利用該拓展點,做了很多初始化操作(並沒有修改BeanDefinition),比如查詢標註了@Autowired@Resource@PostConstruct@PreDestory的屬性和方法,方便後續進行屬性注入和初始化回撥。當然,我們也可以自定義實現,用來修改BeanDefinition資訊或者我們需要的初始化操作,感興趣的小夥伴可以自行試一下哦。 java protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof MergedBeanDefinitionPostProcessor) { MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp; bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName); } } } 04_merged階段的查詢.png

03_merged BeanDefinition.png

1.3 暴露工廠物件

 本階段主要是將早期bean物件提前放入到三級快取singletonFactories中,為迴圈依賴做支援。在後續進行屬性填充時,如果發生迴圈依賴,可以從三級快取中通過getObject()獲取該bean,完成迴圈依賴場景下的自動注入。
java boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } // 做迴圈依賴的支援 將早期例項化bean的ObjectFactory,新增到單例工廠(三級快取)中 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }  該階段完全是為了支撐迴圈依賴的,是Spring為解決迴圈依賴埋的伏筆,在Bean的生命週期中完全可以忽略。這裡為了完整性,和小夥伴們簡單提及一下。

如果對Spring如何解決迴圈依賴不是很清楚的話,可以看筆者的另一篇文章 聊透Spring迴圈依賴,詳細分析了Spring迴圈依賴的解決之道,對本階段的內容也有詳細的敘述。

05_暴露工廠物件.png

1.4 屬性填充

 本階段完成了Spring的核心功能之一:依賴注入,包括自動注入@Autowired注入@Resource注入等。Spring會根據bean的注入模型(預設不自動注入),選擇根據名稱自動注入還是根據型別自動注入。然後呼叫InstantiationAwareBeanPostProcessor#postProcessProperties()完成@Autowired和@Resource的屬性注入。

```java protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { // 省略部分程式碼

// 獲取bean的注入型別 int resolvedAutowireMode = mbd.getResolvedAutowireMode(); // 1: 自動注入 if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); // Add property values based on autowire by name if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_NAME) { // 根據名稱注入 autowireByName(beanName, mbd, bw, newPvs); } // Add property values based on autowire by type if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) { // 根據型別注入 autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; }

// 2: 呼叫BeanPostProcessor,完成@Autowired @Resource屬性填充 PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;

        // 重點: 完成@Autowired @Resource屬性填充
        PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
        if (pvsToUse == null) {
           if (filteredPds == null) {
              // 需要注入的屬性,會過濾掉Aware介面包含的屬性(通過ignoreDependencyInterface新增)
              filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
           }
           pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
           if (pvsToUse == null) {
              return;
           }
        }
        pvs = pvsToUse;
     }
  }

}

// 3: 依賴檢查

if (needsDepCheck) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } checkDependencies(beanName, mbd, filteredPds, pvs); } // 4: 將屬性應用到bean中 if (pvs != null) { applyPropertyValues(beanName, mbd, bw, pvs); } } ```

06_屬性填充詳細.png

06_屬性填充.png

關於依賴注入,筆者在 聊透Spring依賴注入 中有詳細分析,不清楚的小夥伴可以先去感受一下Spring依賴注入的奇妙之處。

1.5 初始化bean

 該階段主要做bean的初始化操作,包括:回撥Aware介面回撥初始化方法生成代理物件等。 - invokeAwareMethods():回撥BeanNameAware、BeanClassLoaderAware、BeanFactoryAware感知介面。 - 回撥後置處理器的前置方法,其中: - ApplicationContextAwareProcessor: 回撥EnvironmentAware、ResourceLoaderAware、ApplicationContextAwareApplicationEventPublisherAware、MessageSourceAware、EmbeddedValueResolverAware感知介面。 - InitDestroyAnnotationBeanPostProcessor:呼叫了標註了@PostConstruct的方法。 - invokeInitMethods()呼叫初始化方法: - 如果bean是InitializingBean的子類, 先呼叫afterPropertiesSet()。 - 回撥自定義的initMethod,比如通過@Bean(initMethod = "xxx")指定的初始化方法。 - 回撥後置處理器的後置方法,可能返回代理物件。其中AbstractAutoProxyCreatorAbstractAdvisingBeanPostProcessor都有可能產生代理物件,比如InfrastructureAdvisorAutoProxyCreator完成了@Transactional代理物件的生成,AsyncAnnotationBeanPostProcessor完成了@Async代理物件的生成。

```java protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) { // 1: 回撥Aware介面中的方法 // 完成Aware方法的回撥(BeanNameAware,BeanClassLoaderAware,BeanFactoryAware) invokeAwareMethods(beanName, bean);

Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // 2: 呼叫before...方法 // ApplicationContextAwareProcessor: 其他Aware方法的回撥 // InitDestroyAnnotationBeanPostProcessor: @PostConstruct方法的回撥 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); }

try { // 3: 完成xml版本和@bean(initMethod)的init方法回撥 invokeInitMethods(beanName, wrappedBean, mbd); }

// 4: 呼叫after方法 // 重點: AOP生成代理物件 if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); }

return wrappedBean; } ``` 08_初始化bean.png

 在初始化完成後,bean會被放到單例池中,正式開始自己的使命:為專案服務,比如接收http請求,進行CRUD等等。後續有使用到該bean的地方,也是直接從單例池中獲取,不會再次建立bean(僅單例的哦)。

2. bean的來龍去脈

2.1 bean的掃描階段

 現在我們已經知道Spring bean是如何建立的了,那什麼時候建立這些bean呢,是遵循懶載入的思想,在實際使用的時候在建立嗎?其實不是的,因為bean之間的複雜關係和生命週期的原因,Spring在容器啟動的時候,就會例項化這些bean,然後放到單例池中,即用即取。並且在建立前、建立中、建立後都會做很多檢查,確保建立的bean是符合要求的,這些我們就不贅述了。

 言歸正傳,細心的你一定發現,建立bean時主要是從RootBeanDefinition mbd這個引數獲取bean的相關資訊的,其實這就是大名鼎鼎的BeanDefinition,其中封裝了關於bean的元資料資訊,關於BeanDefinition,後續我們會單獨講解,這裡我們先理解為bean的元資料資訊即可。那麼這些元資料資訊是什麼時候解析的呢?

 這就要提到Spring的類掃描了,其大致流程是:通過ASM位元組碼技術掃描所有的類 -> 找出需要Sp加了@Compont註解的(簡單理解)-> 封裝成BeanDefinition -> 存放到集合中。後續再例項化bean的時候,就可以遍歷這個集合,獲取到BeanDefinition,然後進行bean的建立了。

09_掃描階段.png

關於處理類掃描的ConfigurationClassPostProcessor後置處理器以及ConfigurationClassParserComponentScanAnnotationParser掃描器的具體細節,後續我們單獨講解,和本章節關係不大,我們先簡單理解即可。

2.2 例項化後回撥

 在前面的章節我們分析過:在容器中的bean例項化,放到單例池中之後,bean在建立階段的生命週期就正式完成,進入使用中階段,開啟對完服務之路。確實,這就是建立bean的全過程,如果有小夥伴看過筆者之前的聊Spring事件的那篇文章(聊透Spring事件機制),會發現對於@EventListener處理器的識別註冊,是在afterSingletonsInstantiated階段完成的。其實這裡也是一個拓展點,我們完全可以實現SmartInitializingSingleton#afterSingletonsInstantiated(),在bean初始化完成後會回撥該方法,進而觸發我們自己的業務邏輯,故這裡我們單獨說一下。不清楚的小夥伴請移步先去了解一下哦。

10_初始化後回撥.png

2.3 bean的銷燬階段

``` protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // ...省略程式碼 try { // 為bean註冊DisposableBean,在容器關閉時,呼叫destory() registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); }

return exposedObject; } `` &emsp;在建立bean的時候,會判斷如果bean是DisposableBeanAutoCloseable的子類,或者有 destroy-method`等,會註冊為可銷燬的bean,在容器關閉時,呼叫對應的方法進行bean的銷燬。

11_bean的銷燬.png