依賴到AOP代理的完整實現(xiàn)原理)
1. 項目概述為什么我們要深挖三級緩存如果你在面試中被問到“Spring是如何解決循環(huán)依賴的”回答“三級緩存”大概率能過關(guān)。但如果你被追問“為什么是三級緩存兩級不行嗎一級不行嗎第二級緩存具體解決了什么問題”還能從容應(yīng)對的才是真正吃透了Spring容器核心設(shè)計的人。這個機制遠不止是面試八股文它是理解Spring Bean生命周期、AOP代理創(chuàng)建乃至框架設(shè)計哲學(xué)的一把鑰匙。我最初接觸Spring源碼時對三級緩存也是一知半解直到在線上環(huán)境遇到一個詭異的Bean創(chuàng)建失敗問題日志指向AbstractAutowireCapableBeanFactory的doCreateBean方法才被迫一頭扎進去。那次排查讓我意識到僅僅知道“三級緩存”這個名詞是遠遠不夠的。它背后是Spring在靈活性支持AOP、性能避免重復(fù)創(chuàng)建和正確性解決循環(huán)依賴之間做出的精妙權(quán)衡。今天我們就拋開那些籠統(tǒng)的概念從源碼行間出發(fā)結(jié)合實際的調(diào)試案例把三級緩存里每一級的作用、交互時機以及設(shè)計者的取舍邏輯徹底掰開揉碎講清楚。無論你是想提升排查問題的能力還是為深入理解Spring框架打下堅實基礎(chǔ)這次探究都會讓你有實實在在的收獲。2. 循環(huán)依賴的本質(zhì)與Spring的解決思路拆解2.1 什么是循環(huán)依賴它真的無解嗎循環(huán)依賴簡單說就是“你中有我我中有你”。比如兩個BeanAService依賴BServiceBService反過來也依賴AService。在傳統(tǒng)的、嚴格的“構(gòu)造-設(shè)置”流程中這似乎是個死結(jié)創(chuàng)建A需要先有B創(chuàng)建B又需要先有A。但從邏輯上看循環(huán)依賴并非無解。關(guān)鍵在于我們需要的并不是一個“完全初始化好的、完美的”Bean而是一個“引用”。只要我能先拿到一個對象的引用即使它內(nèi)部的屬性還沒填完我就可以先把引用給你讓你繼續(xù)你的初始化流程等我自己初始化完成后再把屬性補上。這就像蓋房子兩個房間需要共用一面墻我們不必等兩個房間都完全裝修好再砌墻而是先把墻的框架對象引用立起來讓兩個房間都能基于這個框架繼續(xù)施工最后再統(tǒng)一粉刷墻面屬性注入。Spring解決循環(huán)依賴的核心思想正是“提前暴露引用”。但問題來了暴露一個什么樣的引用是原始對象還是經(jīng)過AOP包裝后的代理對象暴露的時機在哪里如何保證在并發(fā)環(huán)境下所有線程拿到的是同一個、正確的引用三級緩存機制就是為了系統(tǒng)性地回答這些問題而誕生的。2.2 三級緩存全景圖每一級都是精心的設(shè)計在深入代碼前我們先建立全局認知。Spring的三級緩存定義在DefaultSingletonBeanRegistry類中是三個Map/** 一級緩存存放完整的單例Bean */ private final MapString, Object singletonObjects new ConcurrentHashMap(256); /** 二級緩存存放早期的Bean尚未填充屬性用于解決循環(huán)依賴 */ private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16); /** 三級緩存存放ObjectFactory用于生成早期引用可能被AOP增強 */ private final MapString, ObjectFactory? singletonFactories new HashMap(16);一級緩存singletonObjects俗稱“成品庫”。這里存放的是已經(jīng)完全初始化好的Bean經(jīng)歷了實例化、屬性填充、初始化InitializingBean、init-method等所有生命周期步驟。從這取走的Bean是立即可用的。二級緩存earlySingletonObjects俗稱“半成品庫”。這里存放的是已經(jīng)實例化但尚未進行屬性填充和初始化的“早期Bean”對象。它的核心作用是避免重復(fù)執(zhí)行ObjectFactory。當有循環(huán)依賴發(fā)生時其他Bean需要依賴當前Bean的引用時會嘗試從二級緩存獲取。三級緩存singletonFactories這是最精妙的一級。它存放的不是Bean對象本身而是一個ObjectFactory對象工廠。這個工廠的職責是當被調(diào)用時能夠返回當前Bean的“早期引用”。這個引用可能是原始對象但如果該Bean需要被AOP代理那么這個工廠就會返回代理對象。這是支持AOP的關(guān)鍵。關(guān)鍵理解很多人會疑惑有了三級緩存工廠能生成早期引用為什么還需要二級緩存直接讓所有需要早期引用的地方都調(diào)用三級緩存里的工廠不就行了這里涉及一個至關(guān)重要的點性能與一致性。ObjectFactory的執(zhí)行特別是生成代理可能涉及復(fù)雜的邏輯如匹配切面、創(chuàng)建代理。如果每次依賴注入都調(diào)用一次工廠在復(fù)雜的循環(huán)依賴鏈中會導(dǎo)致同一個Bean的代理被創(chuàng)建多次這不僅浪費性能更嚴重的是可能破壞單例語義導(dǎo)致最終拿到的是不同的代理對象。二級緩存的存在就是為了緩存第一次從三級緩存工廠獲取到的結(jié)果無論是原始對象還是代理對象確保后續(xù)所有依賴注入獲取到的是同一個實例。3. 核心流程源碼級解析Bean是如何“誕生”的讓我們跟隨一個普通Bean的創(chuàng)建流程看在循環(huán)依賴的“壓力測試”下三級緩存是如何協(xié)同工作的。核心入口在AbstractBeanFactory.doGetBean而創(chuàng)建單例Bean的主戰(zhàn)場在DefaultSingletonBeanRegistry.getSingleton(String, ObjectFactory)方法。3.1 第一幕嘗試獲取與三級緩存的登場當一個Bean例如AService被請求時Spring首先調(diào)用getSingleton(beanName)。protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 第一步從一級緩存成品庫查找 Object singletonObject this.singletonObjects.get(beanName); if (singletonObject null isSingletonCurrentlyInCreation(beanName)) { // 如果一級緩存沒有且當前Bean正在創(chuàng)建中說明出現(xiàn)了循環(huán)依賴... synchronized (this.singletonObjects) { // 第二步從二級緩存半成品庫查找 singletonObject this.earlySingletonObjects.get(beanName); if (singletonObject null allowEarlyReference) { // 如果二級緩存也沒有且允許早期引用默認true... // 第三步從三級緩存獲取ObjectFactory ObjectFactory? singletonFactory this.singletonFactories.get(beanName); if (singletonFactory ! null) { // 調(diào)用工廠的getObject()這是可能生成代理的地方。 singletonObject singletonFactory.getObject(); // 將結(jié)果放入二級緩存并清空三級緩存對應(yīng)的工廠 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }流程解讀先查一級緩存有則直接返回完美Bean。再查二級緩存沒有完美的看看有沒有“半成品”。有則返回避免重復(fù)創(chuàng)建。最后動用三級緩存如果連半成品都沒有但發(fā)現(xiàn)這個Bean正在創(chuàng)建中isSingletonCurrentlyInCreation為true說明我們撞上了循環(huán)依賴。此時就會取出三級緩存中的ObjectFactory調(diào)用它來生成一個早期引用。這個調(diào)用是觸發(fā)AOP代理創(chuàng)建的關(guān)鍵時機之一。生成后將其放入二級緩存并從三級緩存移除該工廠。3.2 第二幕Bean的創(chuàng)建與三級緩存的填充如果三級緩存都沒找到說明這個Bean是第一次被創(chuàng)建。流程會走到createBean進而到doCreateBean。在doCreateBean方法中有一個決定性的操作protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException { // 1. 實例化通過反射調(diào)用構(gòu)造函數(shù)創(chuàng)建原始對象 instanceWrapper BeanWrapper instanceWrapper createBeanInstance(beanName, mbd, args); Object bean instanceWrapper.getWrappedInstance(); // 2. 【關(guān)鍵步驟】判斷是否允許早期暴露 boolean earlySingletonExposure (mbd.isSingleton() this.allowCircularReferences isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 允許早期暴露向三級緩存添加一個ObjectFactory addSingletonFactory(beanName, () - getEarlyBeanReference(beanName, mbd, bean)); } // 3. 屬性填充Populate Bean這里會解析Autowired、Resource等遞歸觸發(fā)依賴Bean的獲取 populateBean(beanName, mbd, instanceWrapper); // 4. 初始化Initialize Bean調(diào)用InitializingBean.afterPropertiesSet和init-method exposedObject initializeBean(beanName, exposedObject, mbd); // ... 后續(xù)處理 return exposedObject; }核心在于addSingletonFactory這一行。在Bean剛剛實例化完成還是一個“空殼”屬性全是默認值即將進行屬性填充之前Spring將一個ObjectFactory丟進了三級緩存。這個工廠的getObject()方法實際調(diào)用的是getEarlyBeanReference(beanName, mbd, bean)。我們看看getEarlyBeanReference做了什么protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject bean; if (!mbd.isSynthetic() hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp (SmartInstantiationAwareBeanPostProcessor) bp; // 調(diào)用后處理器的getEarlyBeanReference方法 exposedObject ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }這里是AOP登場的舞臺。對于Spring AOP其核心后處理器AbstractAutoProxyCreator就是一個SmartInstantiationAwareBeanPostProcessor。它的getEarlyBeanReference方法會判斷當前Bean是否需要被代理根據(jù)切面定義如果需要它不會立即創(chuàng)建代理而是先將原始Bean包裝在一個“早期代理引用”的持有器中或者在一些策略下直接返回代理對象。這就保證了當循環(huán)依賴發(fā)生時其他Bean注入的將是一個最終會被增強的代理對象的引用而不是原始對象。這是Spring能無縫支持循環(huán)依賴AOP的基石。實操心得調(diào)試時可以在addSingletonFactory和getEarlyBeanReference方法打斷點。你會清晰地看到在AService屬性填充需要BService之前AService的工廠就已經(jīng)進了三級緩存。當后續(xù)流程去創(chuàng)建BService而BService又需要注入AService時就會觸發(fā)上面getSingleton中的流程從三級緩存拿到這個工廠從而獲得AService的早期引用可能是代理。3.3 第三幕循環(huán)依賴的解決與升級到一級緩存我們模擬AService和BService循環(huán)依賴的經(jīng)典場景開始創(chuàng)建AService- 實例化AService對象 - 向三級緩存添加AService的ObjectFactory。開始為AService填充屬性 - 發(fā)現(xiàn)需要BService- 觸發(fā)getBean(“bService”)。開始創(chuàng)建BService- 實例化BService對象 - 向三級緩存添加BService的ObjectFactory。開始為BService填充屬性 - 發(fā)現(xiàn)需要AService- 觸發(fā)getBean(“aService”)。此時getSingleton(“aService”)發(fā)現(xiàn)AService正在創(chuàng)建中isSingletonCurrentlyInCreation為true且一級緩存沒有。于是它從三級緩存拿到AService的ObjectFactory并調(diào)用獲得了AService的早期引用假設(shè)是代理對象。將這個早期引用放入二級緩存并從三級緩存移除AService的工廠。BService成功獲得AService的引用完成屬性填充和初始化最終成為一個完整的Bean被放入一級緩存。流程回溯到AService的屬性填充步驟此時它需要的BService已經(jīng)在一級緩存了直接注入。AService繼續(xù)完成自己的屬性填充和初始化。最后在AService初始化完成后Spring會調(diào)用addSingleton(beanName, singletonObject)方法將AService放入一級緩存并清理二級和三級緩存中關(guān)于AService的所有記錄。至此循環(huán)依賴完美解決兩個Bean都是完整的代理對象如果需要且所有緩存狀態(tài)被正確清理。4. 深度追問設(shè)計抉擇與邊界情況4.1 為什么不能只有兩級緩存假設(shè)我們?nèi)サ舳壘彺嬷挥幸患壋善泛腿壒S。在循環(huán)依賴場景下AService創(chuàng)建工廠入三級緩存。BService創(chuàng)建需要AService從三級緩存調(diào)用工廠得到代理對象proxyA注入給BService。之后如果又有另一個BeanCService也依賴AService且此時AService還未完成初始化未進入一級緩存。那么CService同樣會去三級緩存調(diào)用工廠。問題來了工廠被調(diào)用了兩次。如果getEarlyBeanReference邏輯每次都生成一個新的代理對象那么BService和CService注入的將是兩個不同的AService代理嚴重破壞了單例模式。即使AbstractAutoProxyCreator做了緩存重復(fù)執(zhí)行工廠方法也可能帶來不必要的性能開銷和狀態(tài)不一致的風險。二級緩存充當了“早期引用緩存”的角色確保在Bean完全初始化前所有需要它早期引用的地方拿到的是同一個對象。4.2 為什么不能只有一級緩存如果只有一級緩存根本無法解決循環(huán)依賴。因為只有完全初始化好的Bean才能放入一級緩存。在循環(huán)依賴中兩個Bean都無法完成初始化因為都在等對方先成為“成品”從而陷入死鎖。4.3 構(gòu)造器循環(huán)依賴為何無法解決Spring官方文檔明確說明構(gòu)造器注入的循環(huán)依賴無法解決。原因很簡單三級緩存發(fā)揮作用的前提是對象已經(jīng)實例化。構(gòu)造器注入發(fā)生在實例化階段即調(diào)用new AService(bService)時此時AService對象本身都還沒創(chuàng)建出來更談不上放入三級緩存就需要BService作為構(gòu)造參數(shù)。而為了創(chuàng)建BService又需要AService作為構(gòu)造參數(shù)這就成了一個“先有雞還是先有蛋”的真正死結(jié)。Spring會通過BeanCurrentlyInCreationException提前發(fā)現(xiàn)并拋出異常而不是讓你陷入運行時死循環(huán)。避坑指南這是實際開發(fā)中最常見的循環(huán)依賴問題來源。建議優(yōu)先使用Setter注入或字段注入Autowired。如果非要用構(gòu)造器注入并且確實存在循環(huán)依賴就需要考慮重構(gòu)設(shè)計打破循環(huán)例如引入第三個Bean或者使用Lazy注解進行延遲注入。Lazy注解的原理是它不會在注入點立即去獲取目標Bean而是注入一個代理對象當?shù)谝淮握{(diào)用該代理對象的方法時才會觸發(fā)真實Bean的創(chuàng)建。這相當于將依賴的獲取時機從Bean創(chuàng)建階段推遲到了方法調(diào)用階段從而繞開了構(gòu)造器注入的死鎖。4.4 原型Prototype作用域的Bean為何不支持循環(huán)依賴對于scope”prototype”的BeanSpring容器不負責其完整生命周期的管理每次請求都會創(chuàng)建一個新的實例。因此Spring根本沒有為原型Bean維護任何緩存一級、二級、三級都沒有。當原型Bean A依賴原型Bean B而B又依賴A時在創(chuàng)建A的過程中需要B會觸發(fā)創(chuàng)建B創(chuàng)建B的過程中又需要A這會再次觸發(fā)創(chuàng)建A的新實例……如此遞歸下去直到棧溢出。Spring無法也不應(yīng)該去解決這種場景它會直接拋出BeanCurrentlyInCreationException。5. 實戰(zhàn)調(diào)試與常見問題排查理解了原理我們來看看如何運用這些知識解決實際問題。5.1 調(diào)試技巧觀察緩存狀態(tài)的變化最直觀的學(xué)習(xí)方式就是調(diào)試。在IDEA中對DefaultSingletonBeanRegistry類中的三個Map設(shè)置條件斷點singletonObjects一級緩存earlySingletonObjects二級緩存singletonFactories三級緩存在doCreateBean方法的addSingletonFactory和getSingleton方法的allowEarlyReference邏輯處打上斷點。然后啟動一個包含循環(huán)依賴的簡單Spring應(yīng)用。通過觀察棧幀和這三個Map內(nèi)容的變化你可以像看電影一樣清晰看到Bean的引用是如何在三級緩存中“流動”的。5.2 常見異常與排查思路1. BeanCurrentlyInCreationException這是最常見的與循環(huán)依賴相關(guān)的異?!,F(xiàn)象應(yīng)用啟動失敗報錯信息明確提示BeanCurrentlyInCreationException??赡茉?構(gòu)造器循環(huán)依賴。檢查報錯Bean的依賴關(guān)系看是否使用了構(gòu)造器注入并形成了環(huán)。解決方案改為Setter/字段注入或使用Lazy。可能原因2原型Bean的循環(huán)依賴。檢查Bean的作用域。解決方案重構(gòu)設(shè)計避免原型Bean間的循環(huán)依賴或考慮改為單例。2. 注入的Bean不是代理對象AOP失效現(xiàn)象明明配置了Transactional或自定義切面但方法調(diào)用時切面邏輯不生效調(diào)試發(fā)現(xiàn)注入的對象是原始類型而非代理類型。排查這種情況通常不是三級緩存本身的問題。首先檢查切面配置是否正確如EnableAspectJAutoProxy。其次注意同類方法調(diào)用在同一個Bean內(nèi)部方法A調(diào)用方法B即使方法B有Transactional由于調(diào)用走的是this引用原始對象而非經(jīng)過Spring代理的引用切面也會失效。這是AOP的經(jīng)典問題需要通過AopContext.currentProxy()或重構(gòu)代碼將方法B放到另一個Bean來解決。與三級緩存的關(guān)系確保你的Bean是通過Spring容器獲取的并且循環(huán)依賴能正常走通三級緩存流程。如果循環(huán)依賴因故未能解決可能導(dǎo)致Bean創(chuàng)建失敗或者注入了一個狀態(tài)不正確的對象。3. 在PostConstruct方法中調(diào)用依賴Bean的方法報空指針或狀態(tài)不對現(xiàn)象在AService的PostConstruct方法中調(diào)用了BService的某個方法但BService中的某些依賴比如它依賴的AService似乎還沒注入完成。分析這是由Bean初始化順序?qū)е碌摹ostConstruct在屬性填充之后、初始化回調(diào)之前執(zhí)行。在循環(huán)依賴場景下當AService執(zhí)行PostConstruct時BService可能已經(jīng)創(chuàng)建完成因為它先拿到了AService的早期引用并完成了初始化但BService內(nèi)部持有的AService引用可能還是一個早期對象尚未執(zhí)行PostConstruct的AService。因此如果BService的方法依賴于AService在PostConstruct中初始化的狀態(tài)就可能出錯。建議避免在PostConstruct中進行復(fù)雜的、涉及循環(huán)依賴Bean狀態(tài)邏輯的調(diào)用??梢钥紤]將初始化邏輯移到更靠后的階段或者使用事件監(jiān)聽、SmartInitializingSingleton等機制。5.3 性能考量與最佳實踐三級緩存機制引入了額外的Map操作和可能的代理創(chuàng)建邏輯在極端復(fù)雜的Bean依賴圖中會帶來微小的開銷。但Spring團隊經(jīng)過權(quán)衡認為這對于支持強大的特性循環(huán)依賴、AOP是值得的。最佳實踐建議避免循環(huán)依賴盡管Spring提供了解決方案但循環(huán)依賴本質(zhì)上是一種緊耦合的設(shè)計。在項目設(shè)計中應(yīng)盡量通過重構(gòu)提取公共父類、引入第三方服務(wù)、使用事件驅(qū)動等來避免循環(huán)依賴使架構(gòu)更清晰。優(yōu)先使用Setter/字段注入如果確實存在循環(huán)依賴使用Autowired進行字段注入或Setter注入避免構(gòu)造器注入帶來的無法解決的問題。謹慎使用LazyLazy是打破循環(huán)依賴的利器但它會掩蓋設(shè)計問題并可能將啟動期的問題推遲到運行時。只在確實需要時使用并清楚其影響。理解緩存作用域明確你的Bean是單例默認還是原型。原型Bean的循環(huán)依賴會直接失敗。通過對Spring三級緩存機制的深度解析我們看到的不僅僅是一個解決循環(huán)依賴的技巧更是一個優(yōu)秀框架在面臨復(fù)雜問題時的設(shè)計哲學(xué)通過分層、緩存和延遲決策如通過ObjectFactory延遲代理創(chuàng)建來平衡功能、性能和一致性。下次當你使用Autowired時或許會對背后這套精密的協(xié)作機制多一份敬意也能在遇到相關(guān)問題時更快地直擊要害。