SpringAOP vs. JDK动态代理 vs. CGLib代理 vs. AspectJ代理 @Transactional的失效问题

本文详细介绍了AspectJ作为编译时增强的AOP框架的特点,并对比了SpringAOP的实现方式及其底层动态代理机制,包括JDK动态代理与CGLib的应用场景。此外还分析了@Transactional事务注解在特定条件下的失效原因。

1 AspectJ 是编译时增强的AOP框架,属于静态代理,但是代码组织上优于单纯使用java静态代理设计模式

2 SpringAOP 使用动态代理,与AspectJ没有任何关系,只不过使用了和AspectJ一样的注解,利用类似的概念、语法等(理解为向经典致敬),所以容易误认为SpringAOP使用AspectJ,如SpringAOP中的注解:@Aspect @Around @After,底层完全是SpringAOP自己一套东西


3 Spring 底层使用动态代理,可能是java原生动态代理,也可能是CGLib,默认规则如下
(1)如果实例实现了接口,如XxxServiceImpl implements XxxService,则底层使用JDK动态代理,比如@Transactional加到service方法上是JDK动态代理
(2)如果没有实现接口,类似在XxxController中的Http入口增强方法(如自定义增强注解:@ThirdToken),则使用CGLib代理,如在Http入口方法中答应容器中的XxxController.getClass(),出现如下:XxxController$$EnhancerBySpringCGLIB$$630fe5ae
(3)如果强制使用CGLib,则尽管实现接口类,也强制使用CGLib,但需要配置:

xml:
<aop:aspectj-autoproxy proxy-target-class="true"/>

注解(springboot只要加上AOP依赖后,默认是加上@EnableAspectJAutoProxy,只不过spring.aop.proxy-target-class=false)
@EnableAspectJAutoProxy(proxyTargetClass = true),默认情况下的代理使用原则,即不要配置proxy-target-class="true"

java动态代理主要依靠InvocationHandler实现,CGLib动态代理,主要依靠代理类继承父类,重写父方法实现

4 @Transactional内部调用失效
public class ASerivceImpl implements ASerivce{

     // 自注入
     @Autowared
     private ASerivce aService;
    
     public funcA(){
        this.funcB();
        aService.funcB();
     }

     @Transactional
     public funcB(){
     }
}
调用funcA,funcB上的事务注解是不起效的,因为相当于是this.funcB()是未被Spring增强过的方法,移动要从Spring容器中取出来的才是增强过的方法
 

<think> 根据引用[1][2][3][4]的内容,Spring在以下场景会使用CGLIB代理而非JDK动态代理: 1. **目标类未实现任何接口** - JDK动态代理要求目标类必须实现至少一个接口(通过`Proxy.newProxyInstance()`创建代理时需要传入接口数组)[^1][^4] - 当目标类无接口时,Spring自动切换为CGLIB代理(通过生成目标类的子类实现代理)[^3] ```java @Service // 未实现接口 -> 使用CGLIB public class UserService {...} ``` 2. **强制指定使用CGLIB代理** - 通过`@EnableAspectJAutoProxy(proxyTargetClass = true)`全局配置强制CGLIB[^3] - 或XML配置:`<aop:aspectj-autoproxy proxy-target-class="true"/>` > 此时即使目标类实现了接口,也会使用CGLIB生成子类代理 3. **代理`final`方法以外的所有方法** - JDK代理仅能代理接口方法[^4] - CGLIB代理类中任意非`final`方法(通过继承覆盖)[^2][^3] > 若类中存在`final`方法,该方法不会被增强 4. **需代理非接口方法** - 例如:对具体类中未在接口声明的方法进行事务管理(`@Transactional`)或安全控制 ```java public class OrderService { // 即使OrderService未实现接口,@Transactional仍可生效(依赖CGLIB@Transactional public void internalProcess() {...} } ``` 5. **性能优化场景(历史原因)** - JDK6之前CGLIB性能优于JDK动态代理(ASM字节码生成 vs 反射调用)[^2] - JDK8+后JDK代理性能反超,但Spring仍保留CGLIB的默认兼容逻辑 ### 技术对比总结 | 特性 | JDK动态代理 | CGLIB代理 | |--------------------|--------------------------------|-------------------------------| | 代理机制 | 实现接口 | 继承目标类 | | 目标类要求 | 必须实现接口 | 类不能为`final` | | 方法限制 | 仅代理接口方法 | 代理所有非`final`方法 | | 性能历史 | JDK8+性能更优 | JDK6前性能优势明显[^2] | | 配置方式 | Spring默认(有接口时) | `proxyTargetClass=true`时强制 | > ✅ **验证方法**:通过输出代理类名判断 > - JDK代理:`$Proxy0`(前缀`$Proxy`) > - CGLIB代理:`$$EnhancerBySpringCGLIB$$`(含`EnhancerBySpringCGLIB`)[^3] ```java @Autowired private MyService myService; // 调试代码 System.out.println(myService.getClass().getName()); // 输出示例:com.example.MyService$$EnhancerBySpringCGLIB$$3a7f8d0 ``` §§相关问题§§ 1. 如何通过代码检测当前Bean是否被代理? 2. 为什么CGLIB不能代理`final`方法? 3. `@Transactional`注解在接口方法和类方法上的代理差异? 4. Spring Boot中如何全局配置代理方式? 5. CGLIB代理是否会增加PermGen内存负担? </think> ### Spring 选择 CGLIB 代理而非 JDK 动态代理的场景 根据引用内容[^1][^2][^3][^4],Spring 在以下场景会优先使用 CGLIB 代理: --- #### 一、强制配置场景 1. **显式启用 CGLIB 代理** 通过 `@EnableAspectJAutoProxy(proxyTargetClass = true)` 强制所有 AOP 代理使用 CGLIB: ```java @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) // 强制CGLIB public class AppConfig {...} ``` 或在 XML 中配置: ```xml <aop:aspectj-autoproxy proxy-target-class="true"/> ``` --- #### 二、目标类无接口(默认行为) 2. **代理未实现接口的类** 当目标类**未实现任何接口**时,Spring 自动选择 CGLIBJDK 代理需基于接口)[^3][^4]: ```java @Service // 未实现接口 → 自动用CGLIB public class SimpleService { public void execute() {...} } ``` --- #### 三、代理非接口方法 3. **增强非接口声明的方法** 即使类实现了接口,若增强的方法**不在接口中声明**,必须用 CGLIB 代理: ```java public interface UserService { void save(); } @Service public class UserServiceImpl implements UserService { @Transactional // 此增强方法不在接口中 public void internalProcess() {...} // 需CGLIB代理 @Override public void save() {...} } ``` --- #### 四、代理 final 类外的所有方法 4. **需代理类中所有方法** CGLIB 通过**继承目标类**生成子类代理,可覆盖所有非 `final` 方法[^2]: ```java @Service public class ReportService { public void generate() {...} // 可被代理 public final void export() {...} // final方法无法代理 } ``` --- #### 五、性能敏感场景(历史原因) 5. **JDK 版本低于 6 时** 在 JDK 6 之前,CGLIB(基于 ASM 字节码)比 JDK 动态代理(基于反射)性能更高[^2]。 > 注:JDK 8+ 中 JDK 动态代理性能已反超,但 Spring 保留默认兼容逻辑。 --- ### 技术对比总结 | 场景 | JDK 动态代理 | CGLIB 代理 | |----------------------------|-------------|------------| | 目标类实现接口 | ✓(默认) | 需强制配置 | | 目标类无接口 | ✗ | ✓(默认) | | 代理非接口方法 | ✗ | ✓ | | 代理 final 方法 | ✗ | ✗ | | JDK 6 以下性能优化 | ✗ | ✓ | | 代理类而非接口 | ✗ | ✓ | > ✅ **验证方法**:输出代理类名 > - JDK 代理:`com.sun.proxy.$ProxyX` > - CGLIB 代理:`UserService$$EnhancerBySpringCGLIB$$xxxx` --- ### 配置建议 ```java // 推荐:显式指定代理方式避免歧义 @EnableAspectJAutoProxy(proxyTargetClass = true) // 统一用CGLIB ``` > ⚠️ 注意:CGLIB 会使 `final` 方法、`private` 方法无法被增强[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值