緩存)
“面試官Spring Bean 生命周期詳解”這道題大概是Spring面試中出現(xiàn)頻率最高的前五名。我在幾次技術(shù)招聘里試過(guò)回答基本分成三類(lèi)一類(lèi)是背了流程但順序說(shuō)反一類(lèi)能說(shuō)出實(shí)例化、屬性填充、初始化、銷(xiāo)毀但說(shuō)不清Aware回調(diào)、BeanPostProcessor、InitializingBean這些擴(kuò)展點(diǎn)到底卡在哪一步第三類(lèi)能把這套東西完整講下來(lái)但追到循環(huán)依賴(lài)和三級(jí)緩存就明顯發(fā)虛。這篇文章就是把這道題從“能答”拆到“能講漂亮”。我會(huì)把Bean生命周期的完整節(jié)點(diǎn)、每類(lèi)擴(kuò)展點(diǎn)的設(shè)計(jì)意圖、源碼里的關(guān)鍵路徑還有面試官最可能順藤摸瓜追問(wèn)的三級(jí)緩存原理全部串起來(lái)。適合正在準(zhǔn)備Spring面試的Java開(kāi)發(fā)也適合想真正理解IoC容器工作方式的后端工程師。你完全可以照這個(gè)框架去準(zhǔn)備答完這道題面試官基本不會(huì)再拿“你背過(guò)八股”來(lái)打發(fā)你。1. 先把 Bean 生命周期的全流程跑一遍1.1 從 BeanDefinition 到可用實(shí)例到底經(jīng)歷了什么很多人一上來(lái)就背“實(shí)例化、屬性填充、初始化、銷(xiāo)毀”但漏了最前面的一步Bean進(jìn)入容器管理從BeanDefinition開(kāi)始。Spring容器啟動(dòng)時(shí)先解析XML、注解、JavaConfig等配置信息把它們統(tǒng)一轉(zhuǎn)成BeanDefinition注冊(cè)到BeanFactory的注冊(cè)表里。這一步可理解成“先造好圖紙不急著造產(chǎn)品”。BeanDefinition里面記錄著類(lèi)名、作用域、是否懶加載、初始化方法名、銷(xiāo)毀方法名、依賴(lài)關(guān)系等全部元數(shù)據(jù)。之后所有Bean的創(chuàng)建都是拿著這份圖紙去做的。然后才輪到單個(gè)Bean的創(chuàng)建。在默認(rèn)的ApplicationContext場(chǎng)景下所有非懶加載的單例Bean會(huì)在容器啟動(dòng)階段被批量實(shí)例化也就是refresh()過(guò)程中的finishBeanFactoryInitialization這一步。原型Bean則不著急等每次getBean時(shí)才創(chuàng)建。單個(gè)Bean從創(chuàng)建到銷(xiāo)毀核心流程我習(xí)慣分成十個(gè)節(jié)點(diǎn)實(shí)例化createBeanInstance通過(guò)構(gòu)造器或工廠方法創(chuàng)建對(duì)象此時(shí)對(duì)象剛有內(nèi)存屬性全是默認(rèn)值。合并BeanDefinition并處理注解元數(shù)據(jù)applyMergedBeanDefinitionPostProcessors解析Autowired、Resource、PostConstruct等注解的元數(shù)據(jù)。屬性填充populateBean通過(guò)AutowiredAnnotationBeanPostProcessor等后置處理器完成依賴(lài)注入把Autowired字段、構(gòu)造器參數(shù)、Value配置等賦值進(jìn)去。提前曝光earlySingletonExposure如果是單例且允許循環(huán)引用把ObjectFactory放入三級(jí)緩存。Aware回調(diào)BeanNameAware、BeanClassLoaderAware、BeanFactoryAware由容器直接調(diào)用ApplicationContextAware通過(guò)后置處理器回調(diào)。BeanPostProcessor前置處理postProcessBeforeInitialization被逐個(gè)調(diào)用。自定義初始化實(shí)現(xiàn)了InitializingBean則調(diào)用afterPropertiesSet配置了init-method或Bean(initMethod)則調(diào)用對(duì)應(yīng)方法。BeanPostProcessor后置處理postProcessAfterInitialization被逐個(gè)調(diào)用AOP代理就是在這一步通過(guò)AbstractAutoProxyCreator生成的。使用Bean。容器關(guān)閉時(shí)銷(xiāo)毀PreDestroy、DisposableBean.destroy、destroy-method依次執(zhí)行。注意區(qū)分“實(shí)例化”和“初始化”。實(shí)例化只是new了一個(gè)對(duì)象屬性都還是null真正說(shuō)一個(gè)Bean能干活了要等初始化和屬性填充全部完成。這個(gè)區(qū)分面試時(shí)主動(dòng)說(shuō)出來(lái)就已經(jīng)比一半候選人強(qiáng)了。1.2 一張表看懂各階段的觸發(fā)時(shí)機(jī)與核心機(jī)制把上面對(duì)應(yīng)的機(jī)制和常見(jiàn)實(shí)現(xiàn)整理成一張表方便記憶階段觸發(fā)時(shí)機(jī)核心機(jī)制常見(jiàn)實(shí)現(xiàn)BeanDefinition注冊(cè)容器啟動(dòng)階段配置解析Configuration、ComponentScan實(shí)例化getBean或啟動(dòng)預(yù)實(shí)例化構(gòu)造器/工廠方法new、Scope(prototype)時(shí)按需創(chuàng)建屬性填充實(shí)例化之后、初始化之前BeanPostProcessorAutowired、Resource、Value提前曝光單例創(chuàng)建過(guò)程中三級(jí)緩存循環(huán)依賴(lài)場(chǎng)景用到Aware回調(diào)屬性填充后、初始化前后容器回調(diào)BeanNameAware、ApplicationContextAware初始化屬性填充完成后后置處理器自定義方法PostConstruct、afterPropertiesSet、init-methodAOP代理初始化后BeanPostProcessor動(dòng)態(tài)代理的子類(lèi)化包裝使用初始化完成業(yè)務(wù)調(diào)用—銷(xiāo)毀容器關(guān)閉回調(diào)PreDestroy、DisposableBean、destroy-method這張表最大的價(jià)值是讓你看到Spring的整個(gè)生命周期不是一條“順序調(diào)用”的直線而是圍繞著B(niǎo)eanPostProcessor和容器回調(diào)把多個(gè)機(jī)制拼起來(lái)的。面試時(shí)能畫(huà)出這張表的邏輯就說(shuō)明你不是死記硬背。1.3 BeanFactory 與 ApplicationContext 的生命周期差異很多初學(xué)者不知道上面這套完整流程只有在ApplicationContext包括Spring Boot里的內(nèi)置容器里才會(huì)全量觸發(fā)。如果你用的是最底層的BeanFactory生命周期會(huì)縮水不少。BeanFactory只是最基礎(chǔ)的IoC容器默認(rèn)懶加載Bean默認(rèn)只注冊(cè)少數(shù)內(nèi)置處理器很多擴(kuò)展機(jī)制需要手動(dòng)注冊(cè)。ApplicationContext在BeanFactory之上做了大量增強(qiáng)啟動(dòng)時(shí)預(yù)實(shí)例化單例Bean自動(dòng)注冊(cè)AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、ApplicationContextAwareProcessor等一堆內(nèi)置后置處理器還會(huì)自動(dòng)執(zhí)行BeanFactoryPostProcessor。這個(gè)差異你要能一句話說(shuō)出來(lái)BeanFactory提供的是最小可用的容器能力ApplicationContext提供了企業(yè)級(jí)容器的完整生命周期管理。面試?yán)镒穯?wèn)“你用的BeanFactory還是ApplicationContext”時(shí)能講清這個(gè)區(qū)別會(huì)很加分。2. 擴(kuò)展點(diǎn)為什么設(shè)置這么多拆開(kāi)看設(shè)計(jì)意圖2.1 兩類(lèi) Processor 的角色差異Spring生命周期里最容易搞混的是BeanFactoryPostProcessor和BeanPostProcessor。名字像職責(zé)完全不同。BeanFactoryPostProcessor作用于BeanDefinition階段在所有Bean實(shí)例化之前執(zhí)行你可以修改BeanDefinition本身的屬性、作用域、甚至替換類(lèi)名。最典型的場(chǎng)景就是修改配置類(lèi)中被硬編碼的參數(shù)。舉例來(lái)說(shuō)我曾在不停機(jī)的環(huán)境下用自定義BeanFactoryPostProcessor把某個(gè)第三方依賴(lài)Bean的作用域從singleton改成prototype效果立竿見(jiàn)影。BeanPostProcessor則作用于Bean實(shí)例之后、初始化前后它改的是對(duì)象本身。Autowired注解的解析、AOP代理的生成、PostConstruct方法的調(diào)用全部是通過(guò)各種BeanPostProcessor實(shí)現(xiàn)的。注意BeanFactoryPostProcessor和BeanPostProcessor的執(zhí)行時(shí)機(jī)和對(duì)象模型完全不同一個(gè)是改“圖紙”一個(gè)是改“產(chǎn)品”。面試官很喜歡用這道題來(lái)區(qū)分你是真懂還是背書(shū)這個(gè)區(qū)別自己體會(huì)一下。2.2 Aware 一族讓 Bean 感知容器的時(shí)機(jī)設(shè)計(jì)Aware接口在Spring里是一類(lèi)標(biāo)記接口實(shí)現(xiàn)它的Bean可以從容器中獲取資源。比如BeanNameAware能拿到當(dāng)前Bean在容器中的名字ApplicationContextAware能拿到ApplicationContext對(duì)象。它們的出現(xiàn)是為了解決一個(gè)問(wèn)題Bean明明被容器管理卻不知道容器在哪、自己叫什么。為什么不直接把ApplicationContext注入所有Bean因?yàn)闀?huì)強(qiáng)耦合容器Bean無(wú)法脫離Spring容器使用還會(huì)增加內(nèi)存占用。Aware的設(shè)計(jì)是“按需索取”你實(shí)現(xiàn)了哪個(gè)接口容器才回調(diào)哪個(gè)方法。這跟Autowired自動(dòng)注入風(fēng)格不一樣更像是一種顯式表達(dá)“我需要什么”的聲明。需要注意Aware回調(diào)的時(shí)機(jī)也分兩撥。BeanNameAware、BeanClassLoaderAware、BeanFactoryAware是在initializeBean方法里直接調(diào)用比較早ApplicationContextAware則是通過(guò)ApplicationContextAwareProcessor這個(gè)BeanPostProcessor在postProcessBeforeInitialization階段觸發(fā)稍微靠后一點(diǎn)。在實(shí)際業(yè)務(wù)代碼中我們很少自己實(shí)現(xiàn)ApplicationContextAware因?yàn)镾pring已經(jīng)封裝了ApplicationContextHolder之類(lèi)的工具但作為面試知識(shí)點(diǎn)你得明白它的觸發(fā)原理。2.3 初始化三件套的優(yōu)先級(jí)PostConstruct、afterPropertiesSet、init-method同一個(gè)Bean身上可以同時(shí)用三種方式定義初始化邏輯PostConstruct注解、實(shí)現(xiàn)InitializingBean接口、配置init-method屬性。它們的執(zhí)行順序基本固定PostConstruct先執(zhí)行然后afterPropertiesSet最后init-method。原因也很直接PostConstruct不是Spring自己的注解而是JSR-250標(biāo)準(zhǔn)注解Spring通過(guò)CommonAnnotationBeanPostProcessor把它掛在postProcessBeforeInitialization階段觸發(fā)所以在標(biāo)準(zhǔn)初始化方法之前天然先執(zhí)行。InitializingBean的afterPropertiesSet在Spring源碼里直接調(diào)用init-method是最后定義的兜底方案因此排在最后。如果配置類(lèi)里有這樣一段代碼可以眼見(jiàn)為實(shí)Component public class InitOrderBean implements InitializingBean { PostConstruct public void postConstruct() { System.out.println(1. PostConstruct); } Override public void afterPropertiesSet() { System.out.println(2. InitializingBean.afterPropertiesSet); } BeanInitMethod public void customInit() { System.out.println(3. init-method); } }運(yùn)行后輸出順序就是1、2、3。銷(xiāo)毀階段順序也類(lèi)似PreDestroy先執(zhí)行接著DisposableBean.destroy最后destroy-method。這個(gè)優(yōu)先級(jí)問(wèn)題面試出現(xiàn)的頻率很高強(qiáng)烈建議你親手寫(xiě)一個(gè)Bean跑一遍。真跑一次比背十次都牢。3. 源碼脈絡(luò)從 refresh 到 initializeBean3.1 refresh() 里 Bean 是如何被批量創(chuàng)建的要深入理解生命周期繞不開(kāi)AbstractApplicationContext.refresh()。這是Spring容器的啟動(dòng)入口我們平時(shí)說(shuō)的“容器啟動(dòng)”其實(shí)就是執(zhí)行了refresh()。這個(gè)方法里面有幾個(gè)關(guān)鍵步驟和Bean生命周期直接掛鉤invokeBeanFactoryPostProcessors(beanFactory)執(zhí)行BeanFactoryPostProcessor包括處理Configuration、ComponentScan的ConfigurationClassPostProcessor就是在這步工作的。它是生命周期最靠前的擴(kuò)展點(diǎn)。registerBeanPostProcessors(beanFactory)注冊(cè)各種BeanPostProcessor。注意這里只是注冊(cè)不執(zhí)行執(zhí)行要等Bean實(shí)例化之后。finishBeanFactoryInitialization(beanFactory)實(shí)例化所有非懶加載單例Bean。這一步是單個(gè)Bean生命周期的真正起點(diǎn)。Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 準(zhǔn)備刷新設(shè)置啟動(dòng)時(shí)間、活躍標(biāo)志等 prepareRefresh(); // 創(chuàng)建或獲取 BeanFactory ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory(); // 準(zhǔn)備BeanFactory設(shè)置類(lèi)加載器、表達(dá)式解析器等 prepareBeanFactory(beanFactory); // ... 模板方法子類(lèi)可擴(kuò)展 // 執(zhí)行BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); // 注冊(cè)BeanPostProcessor registerBeanPostProcessors(beanFactory); // 初始化消息源、事件廣播器等 // ... // 實(shí)例化所有非懶加載單例Bean finishBeanFactoryInitialization(beanFactory); // 完成刷新發(fā)布事件 finishRefresh(); } }面試時(shí)不要求背源碼能說(shuō)出refresh()里“先處理BeanFactoryPostProcessor、再注冊(cè)BeanPostProcessor、最后實(shí)例化所有單例Bean”這個(gè)順序就已經(jīng)把生命周期前半場(chǎng)的上下文說(shuō)清楚了。3.2 doCreateBean從實(shí)例化到提前曝光的完整步驟單個(gè)Bean的創(chuàng)建核心邏輯在AbstractAutowireCapableBeanFactory.doCreateBean方法里。這個(gè)方法每一步都有對(duì)應(yīng)的外部擴(kuò)展點(diǎn)可以說(shuō)是Spring生命周期的“五臟六腑”。protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) { BeanWrapper instanceWrapper null; if (mbd.isSingleton()) { instanceWrapper this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper null) { // 1. 實(shí)例化Bean instanceWrapper createBeanInstance(beanName, mbd, args); } Object bean instanceWrapper.getWrappedInstance(); // 2. 提前曝光單例且允許循環(huán)引用時(shí)把ObjectFactory放入三級(jí)緩存 boolean earlySingletonExposure (mbd.isSingleton() this.allowCircularReferences isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, new ObjectFactoryObject() { Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } }); } Object exposedObject bean; try { // 3. 屬性填充 populateBean(beanName, mbd, instanceWrapper); // 4. 初始化 exposedObject initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { // ... } return exposedObject; }注意第2步的提前曝光這是循環(huán)依賴(lài)能成立的基礎(chǔ)但也是很多人在面試中講不出所以然的點(diǎn)。它并沒(méi)有把Bean本身放到緩存而是放了一個(gè)ObjectFactory真正被別人引用時(shí)才調(diào)用getObject()生成“早期引用”。這個(gè)設(shè)計(jì)在后面講三級(jí)緩存時(shí)會(huì)展開(kāi)。3.3 initializeBean 中的四步調(diào)用鏈initializeBean是單個(gè)Bean生命周期的“核心調(diào)度室”它把Aware回調(diào)、前置處理、初始化方法、后置處理串在一起protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { // 1. 直接回調(diào)BeanNameAware、BeanClassLoaderAware、BeanFactoryAware invokeAwareMethods(beanName, bean); // 2. BeanPostProcessor前置處理PostConstruct等方法在這里觸發(fā) Object wrappedBean bean; if (mbd null || !mbd.isSynthetic()) { wrappedBean applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } // 3. 初始化方法InitializingBean.afterPropertiesSet init-method try { invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException(...); } // 4. BeanPostProcessor后置處理AOP代理在這里生成 if (mbd null || !mbd.isSynthetic()) { wrappedBean applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }這一步最關(guān)鍵的是理解調(diào)用順序前置處理在初始化方法之前后置處理在初始化方法之后。AOP代理是在第4步生成的所以如果你在before階段拿到的還是原始對(duì)象在after階段拿到的才可能是代理對(duì)象。很多自定義BeanPostProcessor的代碼里就會(huì)刻意利用這個(gè)時(shí)機(jī)差。4. 生命周期里最大的暗礁循環(huán)依賴(lài)與三級(jí)緩存4.1 循環(huán)依賴(lài)到底卡在哪個(gè)環(huán)節(jié)假如有兩個(gè)BeanA依賴(lài)BB依賴(lài)AA和B互相引用對(duì)方。Spring能創(chuàng)建出A和B嗎答案取決于注入方式。如果是構(gòu)造器注入Spring會(huì)直接拋異常無(wú)法解決如果是setter注入或字段注入Autowired就是在屬性填充階段工作Spring就能通過(guò)三級(jí)緩存把循環(huán)依賴(lài)破掉。為什么構(gòu)造器注入不行因?yàn)锳的構(gòu)造器執(zhí)行時(shí)需要B已經(jīng)存在可此時(shí)B還沒(méi)創(chuàng)建Spring根本拿不到B對(duì)象而A本身也還沒(méi)完成實(shí)例化沒(méi)法提前暴露任何東西。形象點(diǎn)說(shuō)這就好比“你還沒(méi)出生就要先見(jiàn)到一個(gè)需要你出生之后才能見(jiàn)到的人”。setter和字段注入就沒(méi)這個(gè)問(wèn)題它們發(fā)生在populateBean階段此時(shí)A已經(jīng)實(shí)例化完成并且已經(jīng)把ObjectFactory放進(jìn)了三級(jí)緩存。B在創(chuàng)建過(guò)程中需要A時(shí)就能通過(guò)三級(jí)緩存拿回A的早期引用不管A還沒(méi)填充完屬性先把引用用起來(lái)。4.2 三級(jí)緩存解決的是什么問(wèn)題三級(jí)緩存存在于DefaultSingletonBeanRegistry里實(shí)際上就是三個(gè)Map// 一級(jí)緩存最終成品完整的單例Bean MapString, Object singletonObjects new ConcurrentHashMap(256); // 二級(jí)緩存提前曝光的早期引用半成品Bean MapString, Object earlySingletonObjects new ConcurrentHashMap(16); // 三級(jí)緩存可以工廠式生成早期引用的ObjectFactory MapString, ObjectFactory? singletonFactories new HashMap(16);用A和B互相依賴(lài)的場(chǎng)景推演一遍創(chuàng)建AgetSingleton(A)未命中創(chuàng)建A實(shí)例此時(shí)A屬性還是空的。調(diào)用addSingletonFactory把A的ObjectFactory存入三級(jí)緩存。populateBean(A)發(fā)現(xiàn)A依賴(lài)B于是getSingleton(B)創(chuàng)建B實(shí)例B也執(zhí)行addSingletonFactory把B的ObjectFactory存入三級(jí)緩存。populateBean(B)發(fā)現(xiàn)B依賴(lài)A于是getSingleton(A, true)此時(shí)allowEarlyReferencetrue一級(jí)緩存沒(méi)有A二級(jí)緩存沒(méi)有A就從三級(jí)緩存找到A的ObjectFactory調(diào)用getObject()得到早期引用A把早期引用A放入二級(jí)緩存并從三級(jí)緩存刪除然后返回給BB把A注入成功。B完成屬性填充和初始化把完整B放入一級(jí)緩存。A繼續(xù)populateBean拿到B完成填充和初始化把完整A放入一級(jí)緩存。這個(gè)過(guò)程中最關(guān)鍵的是第6步從三級(jí)緩存返回的“早期引用”是一個(gè)還沒(méi)完成初始化的半成品。B拿到這個(gè)半成品去填充屬性沒(méi)問(wèn)題因?yàn)锽只是持有引用并不要求A在那一刻已經(jīng)完全可用。對(duì)應(yīng)源碼里getSingleton的核心邏輯protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject this.singletonObjects.get(beanName); if (singletonObject null isSingletonCurrentlyInCreation(beanName)) { singletonObject this.earlySingletonObjects.get(beanName); if (singletonObject null allowEarlyReference) { synchronized (this.singletonObjects) { singletonObject this.singletonObjects.get(beanName); if (singletonObject null) { singletonObject this.earlySingletonObjects.get(beanName); if (singletonObject null) { ObjectFactory? singletonFactory this.singletonFactories.get(beanName); if (singletonFactory ! null) { singletonObject singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }注意這個(gè)方法里加了一個(gè)synchronized鎖因?yàn)槿?jí)緩存里的singletonFactories不是線程安全的HashMap必須在并發(fā)環(huán)境下鎖住操作。細(xì)節(jié)雖小但面試時(shí)能說(shuō)出來(lái)會(huì)顯得你真的讀過(guò)源碼。4.3 為什么是三級(jí)緩存而不是二級(jí)或一級(jí)這個(gè)問(wèn)題幾乎每逢追問(wèn)必考。我的回答思路分三層。第一層一級(jí)緩存不可能一級(jí)緩存存放的必須是完整可用的單例Bean。早期引用是一個(gè)屬性還沒(méi)填充完的半成品放到一級(jí)緩存等于把一個(gè)殘次品對(duì)外開(kāi)放別的地方拿到的就是錯(cuò)誤數(shù)據(jù)。第二層二級(jí)緩存為什么不夠如果只有二級(jí)緩存意味著在無(wú)循環(huán)依賴(lài)的普通場(chǎng)景里所有單例Bean也必須提前放進(jìn)二級(jí)緩存。那Spring就不得不對(duì)每個(gè)Bean在提前曝光時(shí)都嘗試做AOP代理。這個(gè)行為會(huì)改變AOP的默認(rèn)觸發(fā)時(shí)機(jī)很可能造成代理重復(fù)、代理失效、無(wú)辜對(duì)象被包裝等一堆問(wèn)題。第三層三級(jí)緩存的意義在于“延遲決策”三級(jí)緩存里存的不是Bean而是ObjectFactory。Spring并不知道一個(gè)Bean到底需不需要AOP代理它知道這個(gè)決策要等到真正有人引用它時(shí)才能做。只有在發(fā)生循環(huán)依賴(lài)、提前引用被觸發(fā)的那一刻才調(diào)用getEarlyBeanReference去生成可能被代理過(guò)的早期引用。這個(gè)設(shè)計(jì)既解決了循環(huán)依賴(lài)又保證了AOP代理的正確性。用一句話概括三級(jí)緩存是為了同時(shí)滿足“解決循環(huán)依賴(lài)”和“保持AOP代理時(shí)機(jī)不被打亂”兩個(gè)要求Spring在注冊(cè)表設(shè)計(jì)上給出的最優(yōu)雅方案。這里還要提一個(gè)關(guān)鍵點(diǎn)如果Bean方法上配置了事務(wù)切面、自定義切面等AOP配置在提前曝光階段就會(huì)通過(guò)AbstractAutoProxyCreator生成代理。所以你在開(kāi)發(fā)中看到循環(huán)依賴(lài)的Bean拿到的很可能是一個(gè)“長(zhǎng)得像A”的代理對(duì)象而不是A自己的實(shí)例。5. 面試怎么把這道題講漂亮應(yīng)答框架與追問(wèn)庫(kù)5.1 3到5分鐘的應(yīng)答框架如果你面試時(shí)碰到這道題我建議按“總分總”的方式組織回答控制時(shí)長(zhǎng)在3到5分鐘。先給一句話總覽Bean生命周期可以分成實(shí)例化、屬性填充、初始化、使用、銷(xiāo)毀五個(gè)大階段Spring通過(guò)BeanPostProcessor和各類(lèi)回調(diào)接口把擴(kuò)展點(diǎn)嵌入其中。然后快速過(guò)節(jié)點(diǎn)不要卡頓。從createBeanInstance開(kāi)始一句“屬性填充階段通過(guò)populateBean完成依賴(lài)注入”一句“初始化階段先回調(diào)Aware再走Before后置處理器然后調(diào)InitializingBean最后走After后置處理器”再補(bǔ)一句“AOP代理就是在After后置處理器里通過(guò)AbstractAutoProxyCreator生成的”這時(shí)候面試官眼神已經(jīng)不一樣了。接著主動(dòng)拋出亮點(diǎn)如果讓我補(bǔ)充一個(gè)實(shí)踐點(diǎn)我會(huì)說(shuō)Spring為單例Bean提供了三級(jí)緩存用來(lái)解決循環(huán)依賴(lài)核心是ObjectFactory的延遲決策。最后做個(gè)簡(jiǎn)短收束整個(gè)設(shè)計(jì)本質(zhì)上就是模板方法模式每種擴(kuò)展點(diǎn)都對(duì)應(yīng)一個(gè)特定階段給業(yè)務(wù)留下充分的可定制空間。5.2 高頻追問(wèn)與參考思路追問(wèn)參考思路BeanPostProcessor和BeanFactoryPostProcessor區(qū)別前者改實(shí)例后者改BeanDefinition前者執(zhí)行在實(shí)例化之后后者執(zhí)行在實(shí)例化之前。三級(jí)緩存為什么用ObjectFactory延遲決策代理的產(chǎn)生時(shí)機(jī)保證無(wú)循環(huán)依賴(lài)時(shí)AOP照常有循環(huán)依賴(lài)時(shí)提前生成代理。PostConstruct、afterPropertiesSet、init-method執(zhí)行順序依次執(zhí)行因?yàn)镻ostConstruct掛在postProcessBeforeInitialization上。prototype的Bean會(huì)走完整銷(xiāo)毀回調(diào)嗎不會(huì)Spring不管理prototype的銷(xiāo)毀回調(diào)除非自定義DestructionAwareBeanPostProcessor。構(gòu)造器注入的循環(huán)依賴(lài)為什么不行實(shí)例化前需要參數(shù)此時(shí)還沒(méi)有提前曝光對(duì)象三級(jí)緩存幫不上忙。什么時(shí)候會(huì)觸發(fā)Bean提前曝光單例、允許循環(huán)引用、isSingletonCurrentlyInCreation為true時(shí)。Autowired在哪個(gè)階段生效屬性填充階段通過(guò)AutowiredAnnotationBeanPostProcessor。銷(xiāo)毀回調(diào)順序PreDestroy - DisposableBean.destroy - destroy-method。每個(gè)追問(wèn)的回答后面如果能跟上“為什么”或“源碼里怎么體現(xiàn)”這個(gè)印象分就拿到手了。5.3 容易說(shuō)錯(cuò)的幾個(gè)細(xì)節(jié)我面試時(shí)聽(tīng)到過(guò)很多“差一點(diǎn)但就是不對(duì)”的回答集中在這幾個(gè)細(xì)節(jié)上。第一把PostConstruct說(shuō)成在afterPropertiesSet之后執(zhí)行。前面已經(jīng)說(shuō)過(guò)它是通過(guò)postProcessBeforeInitialization觸發(fā)的所以最先執(zhí)行。第二說(shuō)Bean創(chuàng)建完就立即銷(xiāo)毀。銷(xiāo)毀只有在ApplicationContext關(guān)閉close方法時(shí)才會(huì)觸發(fā)正常運(yùn)行時(shí)Bean常駐容器常駐內(nèi)存不存在創(chuàng)建完就銷(xiāo)毀。第三把BeanPostProcessor的兩個(gè)方法都放在初始化之后。postProcessBeforeInitialization在初始化之前postProcessAfterInitialization在初始化之后這個(gè)先后關(guān)系涉及AOP和Aware回調(diào)說(shuō)錯(cuò)了很致命。第四以為二級(jí)緩存只放AOP代理對(duì)象。實(shí)際上二級(jí)緩存放的是所有被提前引用的半成品Bean只不過(guò)如果Bean需要代理這個(gè)引用是代理對(duì)象。第五說(shuō)“BeanFactory和ApplicationContext生命周期完全一樣”。這個(gè)差異前面章節(jié)講了健忘的話現(xiàn)在翻回去再看一遍。6. 實(shí)戰(zhàn)排查生命周期相關(guān)的坑與埋點(diǎn)技巧6.1 PostConstruct 不執(zhí)行先查這四件事實(shí)際開(kāi)發(fā)中很多人遇到的最詭異問(wèn)題就是“為什么我的PostConstruct方法沒(méi)跑”。排查順序建議從外到內(nèi)。第一依賴(lài)包是否齊全。Spring 5.3.x之前用的是javax.annotationSpring 6.x之后遷移到j(luò)akarta.annotation如果包沒(méi)引或者引錯(cuò)注解根本不會(huì)被識(shí)別。第二使用XML配置時(shí)是否注冊(cè)了CommonAnnotationBeanPostProcessor。用注解驅(qū)動(dòng)開(kāi)發(fā)時(shí)Spring會(huì)自動(dòng)注冊(cè)但是老項(xiàng)目里手工配置BeanFactory時(shí)這步可能被漏掉。第三方法簽名是不是寫(xiě)錯(cuò)了。PostConstruct要求方法無(wú)參數(shù)、非靜態(tài)、無(wú)返回值如果方法帶參數(shù)或者返回了非void在CDI規(guī)范下該方法是無(wú)效的。第四Bean是否真的被容器管理。如果通過(guò)new的方式創(chuàng)建對(duì)象那Bean生命周期自然不生效注解也不會(huì)有任何反應(yīng)。這類(lèi)問(wèn)題在工具類(lèi)中特別常見(jiàn)。6.2 初始化順序錯(cuò)亂與“不可預(yù)測(cè)”的列表Spring對(duì)多個(gè)Bean之間的初始化順序默認(rèn)不保證。如果業(yè)務(wù)邏輯依賴(lài)某個(gè)Bean先初始化必須顯式聲明依賴(lài)關(guān)系。常見(jiàn)的控制方式有四種DependsOn注解指定被依賴(lài)Bean先初始化Order注解控制同一類(lèi)型組件的加載順序?qū)崿F(xiàn)PriorityOrdered接口在BeanFactoryPostProcessor中直接排序。最推薦的是DependsOn它表達(dá)的是“依賴(lài)關(guān)系”而不僅是“先后順序”語(yǔ)義更清晰。我曾在一個(gè)微服務(wù)項(xiàng)目里遇到過(guò)啟動(dòng)時(shí)初始化順序錯(cuò)亂導(dǎo)致監(jiān)聽(tīng)器重復(fù)注冊(cè)的問(wèn)題。排查后確認(rèn)是多個(gè)Runner沒(méi)有控制先后順序后來(lái)統(tǒng)一改成DependsOn加Order雙管齊下問(wèn)題解決。順帶提一句不要在初始化方法里做長(zhǎng)耗時(shí)或需要外部依賴(lài)的操作這會(huì)讓Spring Boot的啟動(dòng)時(shí)間直線上升而且很容易掩蓋真實(shí)依賴(lài)順序的問(wèn)題。6.3 讓生命周期為我所用埋點(diǎn)觀測(cè)的真實(shí)案例如果你想知道每個(gè)Bean初始化耗時(shí)多少完全不用上復(fù)雜的監(jiān)控工具寫(xiě)一個(gè)BeanPostProcessor就能搞定。Component public class LifecycleLogBeanPostProcessor implements BeanPostProcessor { private final MapString, Long startTimeMap new ConcurrentHashMap(); Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { startTimeMap.put(beanName, System.currentTimeMillis()); return bean; } Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Long start startTimeMap.get(beanName); if (start ! null) { long cost System.currentTimeMillis() - start; System.out.println(Bean [ beanName ] 初始化耗時(shí): cost ms); startTimeMap.remove(beanName); } return bean; } }這段代碼會(huì)把所有Bean的初始化耗時(shí)輸出到控制臺(tái)。排查啟動(dòng)慢問(wèn)題時(shí)它就是一把快速定位的尺子。還有一類(lèi)場(chǎng)景在生命周期里特別容易踩雷在DisposableBean的destroy方法里訪問(wèn)其他Bean。銷(xiāo)毀階段不是按創(chuàng)建的反序執(zhí)行的Spring只保證同一個(gè)Bean內(nèi)部回調(diào)順序不保證Bean之間的銷(xiāo)毀順序。如果你的銷(xiāo)毀邏輯依賴(lài)另一個(gè)Bean很可能已經(jīng)拿到一個(gè)正在銷(xiāo)毀中的對(duì)象。我的建議是銷(xiāo)毀邏輯里只做最低限度的清理工作不要把業(yè)務(wù)補(bǔ)償邏輯放在這里。按我的經(jīng)驗(yàn)來(lái)說(shuō)這道題很多人答不好不是不知道流程而是不理解“為什么Spring要設(shè)置這么多擴(kuò)展點(diǎn)”。你準(zhǔn)備的時(shí)候別死背順序而是把Spring想解決的問(wèn)題——對(duì)象創(chuàng)建、依賴(lài)裝配、擴(kuò)展定制、代理增強(qiáng)、銷(xiāo)毀清理——按問(wèn)題去記面試時(shí)從問(wèn)題講機(jī)制給面試官的感覺(jué)就完全是另一個(gè)層次。最后再分享一個(gè)小技巧。準(zhǔn)備這道題時(shí)自己開(kāi)一個(gè)Spring Boot工程定義兩個(gè)互相依賴(lài)的Bean分別實(shí)現(xiàn)InitializingBean、PostConstruct、各寫(xiě)Aware接口再加一個(gè)自定義BeanPostProcessor全項(xiàng)目打上日志。跑一遍你會(huì)看到所有回調(diào)的觸發(fā)順序和對(duì)象狀態(tài)。親手觀察一遍比背十遍八股都牢。等你能解釋為什么需要三級(jí)緩存的時(shí)候你已經(jīng)不只是會(huì)背八股的人而是真正理解IoC容器的那個(gè)人。