聊透 Spring bean 的生命週期
作者:貳師兄的屠宰場
原文:https://juejin.cn/post/7155884227714613285
在對於 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 是如何選擇創建方式的:
// 源碼位於 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 推斷創建方式還是比較聰明的,具體邏輯是:
-
先判斷是否提供了 Supplier,如果提供,則通過 Supplier 產生對象。
-
再判斷是否提供工廠方法,如果提供,則使用工廠方法產生對象。
-
如果都沒提供,需要進行構造方法的推斷,邏輯爲:
-
如果沒有,Spring 默認選擇無參構造方法;
-
如果有,且有
@Autowired(required=true)
的構造方法,就會選擇該構造方法; -
如果有,但是沒有
@Autowired(required=true)
的構造方法,Spring 會從所有加了@Autowired
的構造方法中,根據構造器參數個數、類型匹配程度等綜合打分,選擇一個匹配參數最多,類型最準確的構造方法。 -
如果僅有一個構造方法,會直接使用該構造方法 (
如果構造方法有參數,會自動注入依賴參數
) -
如果有多個構造參數,會判斷有沒有加了
@Autowired
註解的構造參數:
關於創建 bean 時,具體如何選擇構造方法的,本文我們不詳細展開。因爲本文主旨在於分析 bean 的生命週期,我們只需要簡單知道 Spring 會選擇一個構造方法,然後通過反射創建出對象即可。其實在閱讀 Spring 源碼的時候,小夥伴們也一定要學會抓大放小,重點關注核心流程,細枝末節的地方可以先戰術性忽略,後續有需要時再回過頭分析也不遲,千萬不要陷進去,迷失了方向。
這裏給感興趣的小夥伴附上一張流程圖,感興趣的小夥伴也可以留言,後續我們也可以單獨分析。
1.2 merged BeanDefinition
本階段是 Spring 提供的一個拓展點,通過MergedBeanDefinitionPostProcessor
類型的後置處理器,可以對 bean 對應的BeanDefinition
進行修改。Spring 自身也充分利用該拓展點,做了很多初始化操作 (並沒有修改 BeanDefinition),比如查找標註了@Autowired
、 @Resource
、@PostConstruct
、@PreDestory
的屬性和方法,方便後續進行屬性注入和初始化回調。當然,我們也可以自定義實現,用來修改 BeanDefinition 信息或者我們需要的初始化操作,感興趣的小夥伴可以自行試一下哦。
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,完成循環依賴場景下的自動注入。
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 的生命週期中完全可以忽略。這裏爲了完整性,和小夥伴們簡單提及一下。
1.4 屬性填充
本階段完成了 Spring 的核心功能之一:依賴注入,包括自動注入
、@Autowired注入
、@Resource注入
等。Spring 會根據 bean 的注入模型 (默認不自動注入
),選擇根據名稱自動注入還是根據類型自動注入。然後調用InstantiationAwareBeanPostProcessor#postProcessProperties()
完成 @Autowired 和 @Resource 的屬性注入。
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);
}
}
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 代理對象的生成。
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 事件的那篇文章會發現對於@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 的銷燬。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/GU0nlvaY5eg74rLefYhWsQ