聊一聊Spring Bean 的生命週期
來源:江南一點雨
講一講 Spring Bean 的生命週期算是面試時候一道非常經典的問題了!
如果沒有研究過 Spring 原始碼,單純去背面試題,這個問題也是可以回答出來的,但是單純的背缺乏理解,而且面試一緊張,就容易背岔了。但是如果你從頭到尾看了松哥的 Spring 原始碼分析,那麼這個問題就不需要背了,就根據自己對 Spring 原始碼的理解講出來就行了。
在前面的文章中,松哥和大家分析了 Spring 中 Bean 的建立是在 createBean 方法中完成的,在該方法中,真正幹活的實際上是 doCreateBean 方法,具體位置在 AbstractAutowireCapableBeanFactory#doCreateBean,小夥伴們在面試時候常被問到的 Spring Bean 的生命週期,實際上就是問 doCreateBean 方法的執行邏輯。
doCreateBean 方法整體上來說,幹了四件事:
Bean 的例項化。 Bean 屬性填充。 Bean 初始化。 Bean 銷燬方法註冊。
這裡大家注意區分例項化和初始化兩個方法,例項化是指透過反射建立出來 Bean 例項的過程,而初始化則是呼叫一些回撥函式進行 bean 的一些預處理。
1. 例項化
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
這段程式碼的最終目的是為了獲取到一個 bean 例項。獲取之前先去檢查如果有該 bean 尚未完成的 factoryBean 例項就先移除掉。
createBeanInstance 方法就是大家閉著眼睛也能猜出來的透過反射建立 bean 例項過程,最後我們拿到的 bean 例項就是這個 bean。
例項化完成之後,還有兩個小細節。
一個是預留了後置處理器修改 BeanDefinition 的介面,在這裡可以對 BeanDefinition 進行修改,這塊通常用來處理透過註解注入值的情況,這個松哥在之前的文章中也有詳細介紹過,小夥伴們參見:一個特殊的 BeanPostProcessor。
另外一個則是對於迴圈依賴的處理。
松哥之前的文章中已經和小夥伴們詳細分析了迴圈依賴的解決思路,參見:如何透過三級快取解決 Spring 迴圈依賴。
這裡要做的工作就是根據當前 Bean 的情況,將 Bean 存入到三級快取中(二級快取中不存):
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
這塊程式碼的具體含義在之前的文章中松哥都和大家分析過了,這裡就不再囉嗦了,感興趣的小夥伴戳這裡:透過原始碼,捋清楚迴圈依賴到底是如何解決的!。
2. 屬性填充
populateBean(beanName, mbd, instanceWrapper);
這一句就是屬性填充的環節了。屬性填充就是一個 Bean 中我們透過各種註解如 @Autowired 等注入的物件,@Value 注入的字串,這些統一都在 populateBean 中進行處理。具體的程式碼細節松哥在之前的文章中也和大家講過了:@Autowired 到底是怎麼把變數注入進來的?。
3. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
初始化主要是幹這樣四件事:
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
invokeAwareMethods:如果當前 bean 實現了 Aware 介面,那麼 Aware 介面相關的方法就在 invokeAwareMethods 方法中被觸發。 applyBeanPostProcessorsBeforeInitialization:這個是執行 BeanPostProcessor#postProcessBeforeInitialization 方法。 invokeInitMethods:這個裡邊是幹兩件事,如果我們的 Bean 實現了 InitializingBean 介面,那麼該介面中的 afterPropertiesSet 方法就在這裡被觸發;另一方面就是如果我們透過配置檔案 Bean 的初始化方法(XML 檔案中的 init-method 屬性),那麼也會在這裡被觸發。 applyBeanPostProcessorsAfterInitialization:這個是執行 BeanPostProcessor#postProcessAfterInitialization 方法。
這裡需要注意的一點是,透過在 XML 檔案中配置的 init-method 屬性,這個是在第 3 步被觸發執行的;但是如果是透過 @PostConstruct 註解標記的 Bean 的初始化方法,則是透過 BeanPostProcessor 來處理的,具體是在 InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization 方法中處理的。這兩種看起來作用類似的 Bean 初始化方法,底層處理邏輯並不相同。
初始化完成之後,還有一個關於迴圈依賴的處理和判斷。
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
這段程式碼主要是防止 Spring 容器中建立出來的當前 Bean 和被其他 Bean 所依賴的 Bean 不是同一個。例如有 A 和 B 兩個類,Spring 根據既有配置,給 A 生成了代理類,但是 B 引用的並不是 A 的代理物件,而是 A 的原始物件,此時就會有問題。所以這裡主要是去判斷,確保容器中和被使用的 A 是同一個。
檢查的思路就是先去二級快取中查詢,二級快取中如果存在,說明這個 Bean 因為迴圈依賴的原因已經被引用過了(被引用過的 Bean 會存入到二級快取中),此時去判斷 exposedObject 和 bean 是否為同一個 Bean,正常情況下,這兩個當然是同一個 Bean,因為 exposedObject 和 bean 指向同一個記憶體地址。什麼情況下,這兩個 Bean 會不同呢?如果在 Bean 的後置處理器中,我們使用新的 Bean 替換了舊的 Bean,就會導致最終拿到的 exposedObject 和 bean 兩個變數指向的地址不再相同。如果不相同,就要檢查當前 Bean 是否有被容器中的其他 Bean 所依賴了,如果有,並且使用了當前 Bean 的 Bean 還正在建立中,那麼就趕緊刪除掉重新建立,如果使用了當前 Bean 的 Bean 已經建立完成了,那就沒辦法了,只能丟擲異常了。
4. 銷燬
銷燬並不是說要立馬把 Bean 給銷燬掉,這 Bean 剛建立出來還沒使用呢,怎麼就給銷燬了呢?
這裡的銷燬是說把 Bean 的銷燬方法先記錄下來,將來需要銷燬 Bean 或者銷燬容器的時候,就呼叫這些方法去釋放 Bean 所持有的資源。
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
Bean 的銷燬方法我們可以透過註解或者是 XML 檔案進行配置。使用註解的話就是 @PreDestroy 註解,被該註解標記的方法可以在 Bean 銷燬之前執行,我們可以在該方法中釋放資源;也可以使用 XML 檔案進行配置 destroy-method=""
,透過該屬性指定 Bean 銷燬時候需要執行的方法。另外,當前 Bean 也可以透過實現 DisposableBean 介面,並重寫該介面中的 destroy 方法,那麼容器銷燬的時候,這個方法會被自動呼叫以釋放資源。
除了這三種常見的方法之外,還有一個辦法就是如果當前 Bean 實現了 AutoCloseable 介面,那麼當前類中如果存在名為 close 的方法或者名為 shutdown 的方法,那麼對應的方法就會被自動呼叫。
好啦,大致的流程就是這樣了,小夥伴們不妨據此畫一個流程圖看看。
來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70024923/viewspace-3002055/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spring Bean的生命週期SpringBean
- Spring Bean 的生命週期SpringBean
- Spring Bean 生命週期SpringBean
- Spring Bean生命週期SpringBean
- Spring原始碼:bean的生命週期(一)Spring原始碼Bean
- 【Spring】Bean的LifeCycle(生命週期)SpringBean
- Spring中bean的生命週期SpringBean
- 探索Spring系列(一)Spring容器和Bean的生命週期SpringBean
- Spring Bean生命週期,好像人的一生。。SpringBean
- spring bean的作用域和生命週期SpringBean
- Spring原始碼:Bean的生命週期(二)Spring原始碼Bean
- 面試Spring之bean的生命週期面試SpringBean
- Spring原始碼:Bean生命週期(五)Spring原始碼Bean
- Spring原始碼:Bean生命週期(四)Spring原始碼Bean
- Spring原始碼:Bean生命週期(三)Spring原始碼Bean
- Bean的一生(Bean的生命週期)Bean
- bean的生命週期Bean
- Spring中與bean有關的生命週期SpringBean
- 【spring原始碼系列】之【Bean的生命週期】Spring原始碼Bean
- Spring(十二):IOC容器中Bean的生命週期方法SpringBean
- JAVA面試題:Spring中bean的生命週期Java面試題SpringBean
- Spring Bean各階段生命週期的介紹SpringBean
- 淺析spring——IOC 之 分析 Bean 的生命週期SpringBean
- IOC - bean 生命週期Bean
- Bean-生命週期Bean
- Bean 的生命週期回撥Bean
- 詳解Spring中Bean的作用域與生命週期SpringBean
- spring通過註解註冊bean的方式+spring生命週期SpringBean
- Spring的生命週期Spring
- Spring容器啟動流程+Bean的生命週期【附原始碼】SpringBean原始碼
- spring生命週期Spring
- 聊一聊 Spring 中的擴充套件機制(一)Spring套件
- 一次性講清楚spring中bean的生命週期之一:getSingleton方法SpringBean
- spring迴圈依賴解決過程&Bean的生命週期SpringBean
- 簡單地聊一聊Spring Boot的構架Spring Boot
- 劍指Spring原始碼(三)俯瞰Spring的Bean的生命週期(大眾版)Spring原始碼Bean
- Spring的生命週期主Spring
- Spring Boot 啟動原始碼解析結合Spring Bean生命週期分析Spring Boot原始碼Bean