聊透Spring bean的生命週期
在對於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);
}
``
 經過我們跟蹤原始碼,發現Spring推斷建立方式還是比較聰明的,具體邏輯是:
1. 先判斷是否提供了Supplier,如果提供,則通過Supplier產生物件。
2. 再判斷是否提供工廠方法,如果提供,則使用工廠方法產生物件。
3. 如果都沒提供,需要進行構造方法的推斷,邏輯為:
- 如果僅有一個構造方法,會直接使用該構造方法(
如果構造方法有引數,會自動注入依賴引數)
- 如果有多個構造引數,會判斷有沒有加了
@Autowired註解的構造引數:
- 如果沒有,Spring預設選擇無參構造方法;
- 如果有,且有
@Autowired(required=true)的構造方法,就會選擇該構造方法;
- 如果有,但是沒有
@Autowired(required=true)的構造方法,Spring會從所有加了
@Autowired`的構造方法中,根據構造器引數個數、型別匹配程度等綜合打分,選擇一個匹配引數最多,型別最準確的構造方法。
關於建立bean時,具體如何選擇構造方法的,本文我們不詳細展開。因為本文主旨在於分析bean的生命週期,我們只需要簡單知道Spring會選擇一個構造方法,然後通過反射創建出物件即可。其實在閱讀Spring原始碼的時候,小夥伴們也一定要學會抓大放小,重點關注核心流程,細枝末節的地方可以先戰術性忽略,後續有需要時再回過頭分析也不遲,千萬不要陷進去,迷失了方向。
這裡給感興趣的小夥伴附上一張流程圖,感興趣的小夥伴也可以留言,後續我們也可以單獨分析。
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);
}
}
}
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迴圈依賴的解決之道,對本階段的內容也有詳細的敘述。
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); } } ```
關於依賴注入,筆者在 聊透Spring依賴注入 中有詳細分析,不清楚的小夥伴可以先去感受一下Spring依賴注入的奇妙之處。
1.5 初始化bean
該階段主要做bean的初始化操作,包括:回撥Aware介面
、回撥初始化方法
、生成代理物件
等。
- invokeAwareMethods()
:回撥BeanNameAware
、BeanClassLoaderAware、BeanFactoryAware感知介面。
- 回撥後置處理器的前置方法,其中:
- ApplicationContextAwareProcessor: 回撥EnvironmentAware
、ResourceLoaderAware、ApplicationContextAware
、ApplicationEventPublisherAware
、MessageSourceAware、EmbeddedValueResolverAware感知介面。
- InitDestroyAnnotationBeanPostProcessor
:呼叫了標註了@PostConstruct的方法。
- invokeInitMethods()呼叫初始化方法:
- 如果bean是InitializingBean
的子類, 先呼叫afterPropertiesSet()
。
- 回撥自定義的initMethod,比如通過@Bean(initMethod = "xxx")指定的初始化方法。
- 回撥後置處理器的後置方法,可能返回代理物件。其中AbstractAutoProxyCreator
和 AbstractAdvisingBeanPostProcessor
都有可能產生代理物件,比如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;
}
```
在初始化完成後,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的建立了。
關於處理類掃描的
ConfigurationClassPostProcessor
後置處理器以及ConfigurationClassParser
和ComponentScanAnnotationParser
掃描器的具體細節,後續我們單獨講解,和本章節關係不大,我們先簡單理解即可。
2.2 例項化後回撥
在前面的章節我們分析過:在容器中的bean例項化,放到單例池中之後,bean在建立階段的生命週期就正式完成,進入使用中
階段,開啟對完服務之路。確實,這就是建立bean的全過程,如果有小夥伴看過筆者之前的聊Spring事件的那篇文章(聊透Spring事件機制),會發現對於@EventListener
處理器的識別註冊,是在afterSingletonsInstantiated
階段完成的。其實這裡也是一個拓展點,我們完全可以實現SmartInitializingSingleton#afterSingletonsInstantiated()
,在bean初始化完成後會回撥該方法,進而觸發我們自己的業務邏輯,故這裡我們單獨說一下。不清楚的小夥伴請移步先去了解一下哦。
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;
}
``
 在建立bean的時候,會判斷如果bean是
DisposableBean、
AutoCloseable的子類,或者有
destroy-method`等,會註冊為可銷燬的bean,在容器關閉時,呼叫對應的方法進行bean的銷燬。