Spring Boot 預設
proxyTargetClass=true(CGLIB 代理)。事務類被final修飾或事務方法被final修飾,CGLIB 都無法生成代理——類 final 時啟動直接拋AopConfigException,方法 final 時代理能建起來但註解被靜默繞過,比硬報錯更隱蔽。
Issue & Env(案發現場與環境)
- 框架:Spring Boot 2.0+(
spring.aop.proxy-target-class預設為true,所有@TransactionalBean 走 CGLIB 代理) - 場景:事務服務類或事務方法被
final修飾 - 觸發條件:CGLIB 代理的原理是“繼承目標類生成子類,override 業務方法插入攔截器”,這決定了兩條硬限制——final 類無法被繼承,子類都生成不出來;final 方法無法被 override,代理子類裡插不進攔截器,調用直接落到原始方法。
StackTrace(堆疊日誌)
形態 1:類 final → 啟動硬報錯,容易發現。
org.springframework.aop.framework.AopConfigException:
Could not generate CGLIB subclass of class com.example.OrderService:
Common causes of this problem include using a final class or a non-visible class
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:251)
形態 2:方法 final → 事務靜默失效,沒有任何報錯。開 DEBUG 日誌後,正常事務應打出 Creating new transaction,這裡一句都沒有——和自調用失效的表現一致。
Reproduce(復現程式碼)
@Service
public class OrderService {
@Transactional
public final void createOrder(Order o) { orderMapper.insert(o); }
}
啟動正常、調用正常返回,但事務從來沒被開啟。程式碼層面打印驗證:
System.out.println(AopUtils.isAopProxy(this)); // true,类确实被代理了
System.out.println(AopUtils.isCglibProxy(this)); // true
// 但 final 方法调用时事务日志一句没有,说明拦截器没织入这个方法
注意區分:isAopProxy 返回 true 只說明類被代理了,不代表每個方法都被織入。final 方法的失效就藏在這個縫隙裡。
Root Cause(根因分析)
根因在 CGLIB 的字節碼機制:代理子類通過 Enhancer 生成,override 目標方法插入 MethodInterceptor。Java 語言層面 final 方法禁止 override,Enhancer 只能跳過該方法,代理子類裡這個方法直接調用 super.xxx()——攔截器沒機會執行。
快速定位:
# 看事务方法所在类或方法有没有 final
grep -rn "final.*void.*(\|public final class" src/main/java --include="*Service*"
- 啟動拋
Could not generate CGLIB subclass→ 查類的 final 修飾。 - 無報錯但 DEBUG 下沒有
Creating new transaction日誌 → 查方法的 final 修飾,同時排查自調用(兩者現象一致)。
The Fix(解決方案)
方案 A(推薦):去掉 final 修飾
只對需要被代理的類/方法去 final;final 字段不受影響,不必動:
@Service
public class OrderService {
@Transactional
public void createOrder(Order o) { orderMapper.insert(o); } // 去掉 final
}
方案 B:走 JDK 動態代理
類實現了接口時,可以 @Transactional(proxyTargetClass = false) 或 @EnableTransactionManagement(proxyTargetClass = false) 切換到 JDK 動態代理——它基於接口實現,不依賴子類化。但 Boot 預設已統一 CGLIB,混用兩種代理模式容易引入新問題,非必要不推薦。
方案 C:對確實要 final 的場景,用編程式事務繞開
不可去 final 的類(例如被三方約束),方法內用 TransactionTemplate 手工圈事務邊界,繞開註解代理:
transactionTemplate.executeWithoutResult(status -> orderMapper.insert(o));
團隊規範裡應禁止給 Spring Bean 的業務方法加 final,review 階段就能攔住;編程式事務是繞開代理限制的通用兜底。
