建與選型機(jī)制)
1. 從一次代理失效的排查說(shuō)起去年幫一個(gè)朋友排查線上問(wèn)題現(xiàn)象很怪一個(gè)訂單服務(wù)里明明加了Transactional日志里也看到事務(wù)攔截器被調(diào)用了但數(shù)據(jù)庫(kù)里就是沒(méi)回滾。他一開始懷疑是數(shù)據(jù)庫(kù)引擎的問(wèn)題查了半天 InnoDB 配置又懷疑是異常被吞了加了一堆try-catch打印折騰到凌晨?jī)牲c(diǎn)。最后我讓他把斷點(diǎn)打在ProxyFactory的getProxy()方法上一看就明白了——他那個(gè) Bean 是通過(guò)new出來(lái)的壓根沒(méi)走 Spring 容器代理對(duì)象根本沒(méi)生成事務(wù)攔截器自然形同虛設(shè)。這個(gè)坑其實(shí)特別典型。很多人用 Spring AOP 用了好幾年Aspect、Around、Transactional寫得很溜但一旦遇到代理沒(méi)生效切面順序不對(duì)this 調(diào)用不走切面這類問(wèn)題就抓瞎了。根因在于大家只記住了注解怎么用卻沒(méi)搞清楚這些注解背后到底是誰(shuí)在干活。而那個(gè)真正干活的核心角色就是ProxyFactory。ProxyFactory是 Spring AOP 里最被低估的一個(gè)類。它不像Aspect那樣天天出現(xiàn)在業(yè)務(wù)代碼里也不像BeanPostProcessor那樣在面試題里被反復(fù)背誦但它是整個(gè) Spring AOP 創(chuàng)建代理對(duì)象的總調(diào)度室。你寫的每一個(gè)切面、每一個(gè)通知、每一個(gè)Transactional最終都要經(jīng)過(guò)它才能變成一個(gè)能攔截方法調(diào)用的代理對(duì)象。理解ProxyFactory等于拿到了 Spring AOP 源碼的鑰匙——你能看懂代理是怎么選的、通知是怎么排的、攔截器鏈?zhǔn)窃趺创饋?lái)的遇到問(wèn)題也能自己定位而不是靠猜。這篇內(nèi)容適合兩類人一類是已經(jīng)會(huì)用 Spring AOP但想搞清楚底層原理的中高級(jí)開發(fā)者另一類是正在讀 Spring 源碼被ProxyFactory、AdvisedSupport、JdkDynamicAopProxy這些類繞暈的人。我會(huì)從ProxyFactory的定位講起拆解它創(chuàng)建代理的完整鏈路重點(diǎn)講清楚 JDK 動(dòng)態(tài)代理和 CGLIB 的選型邏輯、通知鏈的構(gòu)建過(guò)程以及實(shí)際使用中最容易踩的幾個(gè)坑。全程結(jié)合源碼但不會(huì)貼大段看不懂的代碼而是告訴你每一行代碼在解決什么問(wèn)題。2. ProxyFactory 到底站在 Spring AOP 的哪個(gè)位置2.1 它不是給業(yè)務(wù)用的是給框架自己用的先糾正一個(gè)認(rèn)知偏差。很多人以為ProxyFactory是給開發(fā)者手動(dòng)創(chuàng)建代理用的工具類這個(gè)理解只對(duì)了一半。它確實(shí)可以手動(dòng)用但它的主要服務(wù)對(duì)象是 Spring 框架自身。Aspect注解的解析、Transactional的代理創(chuàng)建、Cacheable的緩存攔截底層統(tǒng)統(tǒng)都是通過(guò)ProxyFactory來(lái)生成代理對(duì)象的。你可以把ProxyFactory理解成一個(gè)代理工廠的標(biāo)準(zhǔn)化流水線。它把我要給某個(gè)對(duì)象加代理這件事拆成了幾個(gè)標(biāo)準(zhǔn)步驟收集通知Advice、確定目標(biāo)對(duì)象TargetSource、選擇代理方式JDK 還是 CGLIB、生成代理對(duì)象。Spring 的各種 AOP 功能本質(zhì)上都是在往這條流水線上投喂不同的原料。從類圖上看ProxyFactory繼承自ProxyCreatorSupport而ProxyCreatorSupport又繼承自AdvisedSupport。這條繼承鏈很關(guān)鍵AdvisedSupport負(fù)責(zé)持有目標(biāo)對(duì)象、通知列表、攔截器列表以及一個(gè)AopProxyFactory。ProxyCreatorSupport負(fù)責(zé)管理Advisor列表并提供createAopProxy()方法來(lái)決定用哪種代理。ProxyFactory則是最外層的門面把上面這些能力包裝成好用的 API。所以當(dāng)你調(diào)用proxyFactory.getProxy()的時(shí)候?qū)嶋H上是沿著這條繼承鏈一路往上最終由ProxyCreatorSupport決定代理策略由JdkDynamicAopProxy或CglibAopProxy完成實(shí)際的字節(jié)碼生成。2.2 和 AspectJ、Aspect 的關(guān)系別搞混這里必須澄清一個(gè)高頻誤解Spring AOP 和 AspectJ 不是一回事。AspectJ 是一套完整的 AOP 解決方案有自己的編譯器和織入機(jī)制能在編譯期、類加載期織入切面。而 Spring AOP 是運(yùn)行時(shí)織入它不修改字節(jié)碼文件而是在運(yùn)行時(shí)動(dòng)態(tài)生成代理對(duì)象。Aspect這個(gè)注解雖然名字來(lái)自 AspectJ但在 Spring 里它只是一個(gè)標(biāo)記告訴 Spring這個(gè)類里定義了切面邏輯。真正解析這個(gè)注解、把里面的Before、After、Around轉(zhuǎn)換成Advice對(duì)象的是 Spring 自己的AnnotationAwareAspectJAutoProxyCreator。而這個(gè) Creator 最終創(chuàng)建代理時(shí)用的還是ProxyFactory。換句話說(shuō)Aspect是語(yǔ)法糖ProxyFactory才是發(fā)動(dòng)機(jī)。你寫Around的時(shí)候覺(jué)得很優(yōu)雅但 Spring 在背后做的事情是解析注解、生成AspectJMethodBeforeAdvice之類的通知對(duì)象、塞進(jìn)ProxyFactory的Advisor列表、然后調(diào)用getProxy()生成代理。理解了這條鏈路你就知道為什么有些切面寫法會(huì)失效——因?yàn)槟愕膶懛ǜ緵](méi)被解析成Advisor自然也就進(jìn)不了ProxyFactory的流水線。2.3 一個(gè)最小可運(yùn)行的 ProxyFactory 示例光講概念太虛直接上一個(gè)能跑的例子。假設(shè)我們有一個(gè)簡(jiǎn)單的服務(wù)接口和實(shí)現(xiàn)public interface OrderService { void createOrder(String orderId); } public class OrderServiceImpl implements OrderService { Override public void createOrder(String orderId) { System.out.println(創(chuàng)建訂單: orderId); } }現(xiàn)在用ProxyFactory手動(dòng)給它加一個(gè)方法執(zhí)行前的通知import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.framework.ProxyFactory; import java.lang.reflect.Method; public class ProxyFactoryDemo { public static void main(String[] args) { OrderServiceImpl target new OrderServiceImpl(); ProxyFactory proxyFactory new ProxyFactory(); proxyFactory.setTarget(target); proxyFactory.addAdvice(new MethodBeforeAdvice() { Override public void before(Method method, Object[] args, Object target) { System.out.println(方法執(zhí)行前攔截: method.getName()); } }); OrderService proxy (OrderService) proxyFactory.getProxy(); proxy.createOrder(ORDER-001); } }運(yùn)行結(jié)果會(huì)先打印方法執(zhí)行前攔截: createOrder再打印創(chuàng)建訂單: ORDER-001。這個(gè)例子雖然簡(jiǎn)單但它完整走了一遍ProxyFactory的核心流程設(shè)置目標(biāo)、添加通知、生成代理、調(diào)用方法。后面要講的所有源碼細(xì)節(jié)都是在這個(gè)骨架上展開的。注意這個(gè)例子默認(rèn)走的是 JDK 動(dòng)態(tài)代理因?yàn)镺rderServiceImpl實(shí)現(xiàn)了接口。如果你把接口去掉只留實(shí)現(xiàn)類ProxyFactory會(huì)自動(dòng)切換到 CGLIB。這個(gè)切換邏輯就是下一節(jié)要重點(diǎn)講的。3. JDK 動(dòng)態(tài)代理還是 CGLIBProxyFactory 的選型邏輯3.1 選型發(fā)生在 createAopProxy 方法里代理方式的選擇不是隨機(jī)的也不是你隨便配的而是由ProxyCreatorSupport.createAopProxy()方法根據(jù)一組條件計(jì)算出來(lái)的。核心判斷邏輯在DefaultAopProxyFactory里簡(jiǎn)化后的邏輯是這樣的public AopProxy createAopProxy(AdvisedSupport config) { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class? targetClass config.getTargetClass(); if (targetClass null) { throw new AopConfigException(TargetSource cannot determine target class); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }這段代碼信息量很大我拆開講。三個(gè)條件只要滿足任意一個(gè)就會(huì)進(jìn)入可能用 CGLIB的分支isOptimize()是否開啟了優(yōu)化模式這個(gè)一般不用除非你明確知道自己在做什么。isProxyTargetClass()是否強(qiáng)制代理目標(biāo)類。EnableAspectJAutoProxy(proxyTargetClass true)或者 Spring Boot 2.x 之后默認(rèn)的spring.aop.proxy-target-classtrue都會(huì)讓這個(gè)值為 true。hasNoUserSuppliedProxyInterfaces()目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)任何用戶自定義接口。進(jìn)入分支后還要再看一眼目標(biāo)類本身如果目標(biāo)類本身就是個(gè)接口或者已經(jīng)是個(gè)代理類那還是用 JDK 動(dòng)態(tài)代理否則才用 CGLIB。3.2 為什么 Spring Boot 默認(rèn)改成了 CGLIB這是很多人困惑的點(diǎn)。Spring 早期默認(rèn)用 JDK 動(dòng)態(tài)代理但從 Spring Boot 2.x 開始默認(rèn)改成了 CGLIB。原因其實(shí)很實(shí)際JDK 動(dòng)態(tài)代理要求目標(biāo)類必須實(shí)現(xiàn)接口而現(xiàn)實(shí)中有大量業(yè)務(wù)類是不實(shí)現(xiàn)接口的比如各種XxxService直接就是 class。如果默認(rèn)用 JDK 代理這些類就沒(méi)法被代理開發(fā)者得手動(dòng)加接口或者改配置體驗(yàn)很差。CGLIB 通過(guò)繼承目標(biāo)類來(lái)生成子類代理不要求接口適用面更廣。代價(jià)是它不能代理final類和方法因?yàn)槔^承不了。所以 Spring Boot 選擇 CGLIB 作為默認(rèn)是在通用性和限制之間做的權(quán)衡。這里有個(gè)實(shí)操經(jīng)驗(yàn)如果你的項(xiàng)目里大量使用了接口 實(shí)現(xiàn)類的分層結(jié)構(gòu)其實(shí)用 JDK 動(dòng)態(tài)代理更輕量生成的代理類更小啟動(dòng)也更快??梢栽谂渲美镲@式關(guān)掉 CGLIBspring: aop: proxy-target-class: false但要注意一旦關(guān)掉所有沒(méi)實(shí)現(xiàn)接口的 Bean 就無(wú)法被 AOP 代理了Transactional會(huì)靜默失效。這個(gè)坑我見過(guò)不止一次排查起來(lái)很費(fèi)勁因?yàn)椴粓?bào)錯(cuò)只是不生效。3.3 兩種代理方式的性能與限制對(duì)比對(duì)比維度JDK 動(dòng)態(tài)代理CGLIB代理前提目標(biāo)類必須實(shí)現(xiàn)接口目標(biāo)類不能是 final方法不能是 final代理機(jī)制實(shí)現(xiàn)相同接口委托調(diào)用繼承目標(biāo)類覆寫方法生成速度較快較慢需要生成子類字節(jié)碼調(diào)用速度反射調(diào)用略慢直接調(diào)用略快代理類數(shù)量每個(gè)接口一個(gè)代理類每個(gè)目標(biāo)類一個(gè)代理類典型失效場(chǎng)景目標(biāo)類無(wú)接口目標(biāo)類是 final 或方法 final關(guān)于調(diào)用速度網(wǎng)上很多文章說(shuō) CGLIB 比 JDK 快這個(gè)結(jié)論要加限定條件。在 JDK 8 之后JDK 動(dòng)態(tài)代理的性能已經(jīng)大幅優(yōu)化兩者差距很小。而且 Spring 在 CGLIB 代理里也做了緩存實(shí)際業(yè)務(wù)中這點(diǎn)差異基本可以忽略。選型時(shí)更應(yīng)該關(guān)注的是能不能代理而不是哪個(gè)快。還有一個(gè)容易被忽略的點(diǎn)CGLIB 代理會(huì)創(chuàng)建目標(biāo)類的子類這意味著目標(biāo)類的構(gòu)造函數(shù)會(huì)被調(diào)用兩次一次是原始 Bean一次是代理子類。如果你的構(gòu)造函數(shù)里有副作用比如注冊(cè)監(jiān)聽器、啟動(dòng)線程可能會(huì)出問(wèn)題。這個(gè)坑在寫一些有狀態(tài)的服務(wù)時(shí)特別容易踩。4. 通知鏈?zhǔn)窃趺匆徊讲綐?gòu)建起來(lái)的4.1 從 addAdvice 到攔截器鏈的轉(zhuǎn)換ProxyFactory提供了addAdvice()和addAdvisor()兩個(gè)方法看起來(lái)差不多但內(nèi)部處理邏輯不同。addAdvice()添加的是裸的Advice對(duì)象Spring 會(huì)把它包裝成一個(gè)DefaultPointcutAdvisorpointcut 是Pointcut.TRUE也就是匹配所有方法。而addAdvisor()添加的是已經(jīng)帶好 pointcut 的Advisor匹配邏輯由你自己控制。真正關(guān)鍵的是AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice()這個(gè)方法。它做的事情是遍歷所有Advisor用AdvisorChainFactory把每個(gè)Advisor轉(zhuǎn)換成MethodInterceptor最終形成一個(gè)攔截器鏈。這個(gè)轉(zhuǎn)換過(guò)程有個(gè)細(xì)節(jié)如果一個(gè)Advisor的 pointcut 是動(dòng)態(tài)的比如基于運(yùn)行時(shí)參數(shù)判斷那它會(huì)被包裝成InterceptorAndDynamicMethodMatcher在調(diào)用時(shí)還要再做一次匹配。為什么要轉(zhuǎn)成MethodInterceptor因?yàn)镸ethodInterceptor是 AOP 聯(lián)盟定義的標(biāo)準(zhǔn)接口只有一個(gè)invoke(MethodInvocation invocation)方法。所有的通知類型——前置、后置、環(huán)繞、異常、返回——最終都會(huì)被適配成MethodInterceptor。比如MethodBeforeAdvice會(huì)被MethodBeforeAdviceInterceptor包裝AfterReturningAdvice會(huì)被AfterReturningAdviceInterceptor包裝。這樣整個(gè)鏈路上跑的都是統(tǒng)一類型調(diào)用邏輯就簡(jiǎn)化了。4.2 攔截器鏈的執(zhí)行順序與責(zé)任鏈模式攔截器鏈構(gòu)建好之后執(zhí)行時(shí)用的是責(zé)任鏈模式。以JdkDynamicAopProxy.invoke()為例核心邏輯是public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation invocation new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); return invocation.proceed(); }ReflectiveMethodInvocation.proceed()是責(zé)任鏈的核心。它維護(hù)了一個(gè)currentInterceptorIndex游標(biāo)每次調(diào)用proceed()就把游標(biāo)加一取出下一個(gè)攔截器執(zhí)行。如果游標(biāo)已經(jīng)到鏈尾就直接反射調(diào)用目標(biāo)方法。每個(gè)攔截器在執(zhí)行完自己的邏輯后可以選擇繼續(xù)調(diào)用invocation.proceed()也可以直接返回從而中斷鏈路。這個(gè)設(shè)計(jì)帶來(lái)的一個(gè)重要特性是攔截器的順序決定了通知的執(zhí)行順序。Around通知如果排在前面它的proceed()之前的代碼會(huì)在所有后續(xù)通知之前執(zhí)行proceed()之后的代碼會(huì)在所有后續(xù)通知之后執(zhí)行。這就是為什么Order注解能控制切面順序——它影響的是Advisor在列表里的排序進(jìn)而影響攔截器鏈的順序。4.3 一個(gè)容易搞錯(cuò)的順序問(wèn)題很多人以為Before一定在Around之前執(zhí)行其實(shí)不一定。執(zhí)行順序取決于Advisor的排序。如果Around切面的Order值更小它會(huì)先執(zhí)行它的proceed()之前的部分會(huì)先于Before執(zhí)行。我見過(guò)一個(gè)案例有人在Around里做了參數(shù)校驗(yàn)在Before里做了日志記錄結(jié)果發(fā)現(xiàn)日志里記錄的參數(shù)是校驗(yàn)前的原始參數(shù)而不是校驗(yàn)后修改的參數(shù)。原因就是Around的 order 更小先執(zhí)行了proceed()之后才輪到Before。要控制這個(gè)順序最穩(wěn)妥的方式是顯式給切面加Order并且理解order 越小越靠外這個(gè)規(guī)則??客獾那忻嫫鋚roceed()之前的邏輯最先執(zhí)行proceed()之后的邏輯最后執(zhí)行??梢园阉胂蟪裳笫[最外層的是 order 最小的切面。5. 手動(dòng)使用 ProxyFactory 時(shí)最容易踩的四個(gè)坑5.1 目標(biāo)對(duì)象為 null 導(dǎo)致的空指針ProxyFactory允許你先設(shè)置通知再設(shè)置目標(biāo)但如果在getProxy()之前目標(biāo)還是 null生成的代理在調(diào)用方法時(shí)會(huì)拋NullPointerException。更隱蔽的情況是你設(shè)置了TargetSource但TargetSource.getTarget()返回 null。這種情況在池化目標(biāo)對(duì)象比如PoolingTargetSource時(shí)可能出現(xiàn)池空了就會(huì)返回 null。排查這類問(wèn)題的技巧是在getProxy()之前打印一下proxyFactory.getTargetSource().getTarget()確認(rèn)目標(biāo)對(duì)象確實(shí)存在。如果用的是自定義TargetSource要確保getTarget()方法在目標(biāo)不可用時(shí)拋出明確異常而不是返回 null。5.2 代理對(duì)象被再次代理導(dǎo)致的通知重復(fù)這個(gè)坑在手動(dòng)使用ProxyFactory時(shí)特別常見。假設(shè)你有一個(gè)已經(jīng)代理過(guò)的對(duì)象又把它作為目標(biāo)傳給一個(gè)新的ProxyFactory那最終生成的代理會(huì)包含兩層攔截器鏈通知會(huì)執(zhí)行兩次。在 Spring 容器里AbstractAutoProxyCreator會(huì)通過(guò)AopUtils.isAopProxy()判斷對(duì)象是否已經(jīng)是代理避免重復(fù)代理。但手動(dòng)使用時(shí)沒(méi)有這層保護(hù)。判斷一個(gè)對(duì)象是否已經(jīng)是代理可以用AopUtils.isAopProxy(obj)或AopUtils.isJdkDynamicProxy(obj)、AopUtils.isCglibProxy(obj)。在手動(dòng)創(chuàng)建代理前加一道判斷能避免很多詭異問(wèn)題。5.3 this 調(diào)用不走代理的經(jīng)典問(wèn)題這個(gè)問(wèn)題在 Spring 容器里也常見但手動(dòng)用ProxyFactory時(shí)更容易理解。假設(shè)目標(biāo)類里有兩個(gè)方法a()和b()a()內(nèi)部調(diào)用了this.b()。當(dāng)你通過(guò)代理對(duì)象調(diào)用a()時(shí)a()的調(diào)用會(huì)被攔截但a()內(nèi)部通過(guò)this調(diào)用的b()不會(huì)走代理因?yàn)閠his指向的是原始目標(biāo)對(duì)象不是代理對(duì)象。解決辦法有三種一是把b()的調(diào)用改成通過(guò)代理對(duì)象調(diào)用注入自身或AopContext.currentProxy()二是用AspectJ的編譯期織入三是重新設(shè)計(jì)把b()的邏輯抽到另一個(gè) Bean 里。第一種方案需要開啟exposeProxyproxyFactory.setExposeProxy(true);然后在代碼里用AopContext.currentProxy()獲取當(dāng)前代理。但要注意exposeProxy會(huì)把代理對(duì)象存到ThreadLocal里用完必須清理否則會(huì)有內(nèi)存泄漏風(fēng)險(xiǎn)。Spring 在invoke()方法里用try-finally保證了清理手動(dòng)使用時(shí)也要注意這一點(diǎn)。5.4 通知里拋異常導(dǎo)致的事務(wù)回滾失效這個(gè)坑和ProxyFactory本身關(guān)系不大但和攔截器鏈的執(zhí)行密切相關(guān)。如果Around通知里捕獲了異常但沒(méi)有重新拋出那后續(xù)的Transactional攔截器就感知不到異常事務(wù)不會(huì)回滾。因?yàn)槭聞?wù)回滾依賴的是異常傳播到TransactionInterceptor如果異常在更外層的切面被吞了事務(wù)攔截器就以為一切正常。正確的做法是如果Around里需要處理異常要么處理后重新拋出要么明確知道自己在做什么。我一般建議在Around里只做日志和監(jiān)控不要吞異常。如果確實(shí)需要轉(zhuǎn)換異常類型要確保轉(zhuǎn)換后的異常是RuntimeException或者配置了rollbackFor。6. 從源碼看代理對(duì)象的生成時(shí)機(jī)與緩存6.1 getProxy 方法做了什么ProxyFactory.getProxy()最終調(diào)用的是ProxyCreatorSupport.createAopProxy()拿到AopProxy后調(diào)用其getProxy()方法。以JdkDynamicAopProxy為例它的getProxy()邏輯是public Object getProxy(ClassLoader classLoader) { Class?[] proxiedInterfaces AopProxyUtils.completeProxiedInterfaces(this.advised, true); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); }關(guān)鍵在completeProxiedInterfaces()它會(huì)收集目標(biāo)對(duì)象實(shí)現(xiàn)的所有接口再加上 Spring 自己需要的SpringProxy、Advised、DecoratingProxy這幾個(gè)接口。為什么要加這幾個(gè)接口因?yàn)?Spring 需要通過(guò)它們來(lái)判斷一個(gè)對(duì)象是不是代理、獲取代理的Advisor列表、獲取最終的目標(biāo)類。這也是為什么你可以把一個(gè)代理對(duì)象強(qiáng)轉(zhuǎn)成Advised然后調(diào)用getAdvisors()查看它有哪些通知。6.2 代理類的緩存機(jī)制JDK 動(dòng)態(tài)代理本身有緩存Proxy.newProxyInstance()生成的代理類會(huì)被緩存在WeakCache里key 是類加載器和接口數(shù)組。CGLIB 也有緩存Spring 在CglibAopProxy里用MapClass?, Constructor?緩存了代理類的構(gòu)造函數(shù)避免每次都重新生成。但要注意這個(gè)緩存是代理類級(jí)別的不是代理對(duì)象級(jí)別的。每次調(diào)用getProxy()都會(huì)創(chuàng)建一個(gè)新的代理對(duì)象只是復(fù)用了代理類的字節(jié)碼。所以如果你在循環(huán)里反復(fù)調(diào)用getProxy()會(huì)創(chuàng)建大量代理對(duì)象雖然代理類只生成一次但對(duì)象創(chuàng)建本身也有開銷。正確的做法是代理對(duì)象創(chuàng)建一次緩存起來(lái)復(fù)用。在 Spring 容器里這個(gè)緩存由AbstractAutoProxyCreator的proxyTypes和SingletonBeanRegistry共同保證每個(gè)單例 Bean 只會(huì)被代理一次。手動(dòng)使用時(shí)你需要自己保證這一點(diǎn)。6.3 一個(gè)驗(yàn)證代理類型的實(shí)用方法調(diào)試 AOP 問(wèn)題時(shí)快速確認(rèn)一個(gè)對(duì)象用的是哪種代理能省很多時(shí)間??梢詫懸粋€(gè)工具方法public static String getProxyType(Object obj) { if (AopUtils.isJdkDynamicProxy(obj)) { return JDK動(dòng)態(tài)代理; } else if (AopUtils.isCglibProxy(obj)) { return CGLIB代理; } else if (AopUtils.isAopProxy(obj)) { return 其他AOP代理; } else { return 非代理對(duì)象; } }在排查為什么事務(wù)不生效時(shí)先打印一下 Bean 的代理類型如果顯示非代理對(duì)象那基本可以確定是代理沒(méi)生成問(wèn)題出在配置或 Bean 的創(chuàng)建方式上而不是事務(wù)本身。這個(gè)技巧我用了很多次每次都能快速縮小排查范圍。7. 幾個(gè)真實(shí)場(chǎng)景下的排查思路7.1 切面不生效的通用排查鏈路遇到切面不生效我一般按這個(gè)順序排查確認(rèn)目標(biāo) Bean 是否被 Spring 管理。如果是new出來(lái)的或者通過(guò)Bean方法返回但沒(méi)走容器代理不會(huì)生成。確認(rèn)切面類是否被 Spring 掃描到。Aspect類需要是一個(gè) Bean通常加Component。確認(rèn)代理類型是否匹配。如果目標(biāo)類沒(méi)接口且用了 JDK 代理代理不會(huì)生成。確認(rèn) pointcut 表達(dá)式是否匹配??梢杂肁opUtils.canApply()手動(dòng)驗(yàn)證。確認(rèn)方法是否是final或static。這兩種方法無(wú)法被 CGLIB 代理。確認(rèn)調(diào)用是否走了代理。this調(diào)用不走代理。這個(gè)鏈路覆蓋了 90% 以上的切面失效場(chǎng)景。按順序排查基本都能定位到根因。7.2 循環(huán)依賴與代理的相互影響Spring 解決循環(huán)依賴時(shí)會(huì)提前暴露一個(gè)早期引用。如果這個(gè) Bean 需要被 AOP 代理早期引用暴露的是代理對(duì)象還是原始對(duì)象答案是如果代理創(chuàng)建發(fā)生在postProcessAfterInitialization階段那早期引用暴露的是原始對(duì)象后續(xù)注入到其他 Bean 里的是代理對(duì)象這會(huì)導(dǎo)致同一個(gè) Bean 在不同地方類型不一致。Spring 通過(guò)getEarlyBeanReference()方法解決了這個(gè)問(wèn)題在提前暴露引用時(shí)如果 Bean 需要被代理就先創(chuàng)建代理對(duì)象并暴露出去。這樣其他 Bean 注入到的就是代理對(duì)象保持一致。理解這一點(diǎn)對(duì)排查循環(huán)依賴相關(guān)的 AOP 問(wèn)題很有幫助。7.3 性能敏感場(chǎng)景下的代理選擇在高頻調(diào)用的場(chǎng)景下代理帶來(lái)的開銷不能忽略。JDK 動(dòng)態(tài)代理每次調(diào)用都要走反射CGLIB 雖然直接調(diào)用但代理類的生成和類加載也有成本。如果某個(gè)方法每秒被調(diào)用幾十萬(wàn)次代理的開銷可能會(huì)顯現(xiàn)出來(lái)。優(yōu)化思路有幾個(gè)一是盡量縮小切面的匹配范圍只攔截必要的方法減少攔截器鏈的長(zhǎng)度二是對(duì)于性能極度敏感的方法考慮不用 AOP直接手寫裝飾器三是用Around時(shí)避免在通知里做重操作比如每次都查數(shù)據(jù)庫(kù)。我見過(guò)一個(gè)案例有人在Around里每次都打印完整的方法參數(shù)和返回值日志量巨大導(dǎo)致性能下降了一半。把日志級(jí)別調(diào)高或者改成采樣打印后性能恢復(fù)正常。8. 我個(gè)人的幾點(diǎn)使用體會(huì)ProxyFactory這個(gè)類我建議每個(gè)用 Spring 的開發(fā)者都花時(shí)間讀一遍源碼。它不長(zhǎng)核心邏輯就那么幾百行但它是理解 Spring AOP 的最佳入口。讀懂了它你再去看Transactional、Cacheable、Async這些注解的實(shí)現(xiàn)會(huì)發(fā)現(xiàn)它們都是同一套模式的變體解析注解生成Advisor塞進(jìn)ProxyFactory生成代理攔截器鏈執(zhí)行。手動(dòng)使用ProxyFactory的場(chǎng)景其實(shí)不多主要是在寫框架擴(kuò)展、自定義 AOP 邏輯、或者做單元測(cè)試時(shí)。但即使不手動(dòng)用理解它的工作原理也能幫你快速定位問(wèn)題。我自己的習(xí)慣是遇到 AOP 相關(guān)的詭異問(wèn)題先在getProxy()和invoke()兩個(gè)方法上打斷點(diǎn)看代理有沒(méi)有生成、攔截器鏈里有哪些攔截器、執(zhí)行順序是什么。這兩個(gè)斷點(diǎn)基本能解決大部分問(wèn)題。最后分享一個(gè)小技巧如果你想看某個(gè) Bean 最終被哪些Advisor代理了可以把它強(qiáng)轉(zhuǎn)成Advised然后打印getAdvisors()。這個(gè)方法在排查為什么這個(gè)切面沒(méi)生效時(shí)特別有用能直接看到實(shí)際生效的Advisor列表而不是靠猜。