StackTraces
Spring Boot已驗證

Spring Boot final 方法 CGLIB 代理失敗致事務失效

@debug_master27 天前更新閱讀 6 min00

Spring Boot 預設 proxyTargetClass=true(CGLIB 代理)。事務類被 final 修飾或事務方法被 final 修飾,CGLIB 都無法生成代理——類 final 時啟動直接拋 AopConfigException,方法 final 時代理能建起來但註解被靜默繞過,比硬報錯更隱蔽。

Issue & Env(案發現場與環境)

  • 框架:Spring Boot 2.0+(spring.aop.proxy-target-class 預設為 true,所有 @Transactional Bean 走 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 階段就能攔住;編程式事務是繞開代理限制的通用兜底。

References(參考來源)

最後更新於 2026-09-14

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法