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

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法