Spring AOP如何通过CGLIB和JDK动态代理实现方法拦截? 在什么情况下会选择使用CGLIB代理?
2条回答 默认 最新
檀越@新空间 2025-12-05 09:35关注上午好☀️☀️☀️️
本答案参考通义千问Spring AOP(面向切面编程)通过 JDK动态代理 和 CGLIB 实现对方法的拦截,其核心机制是基于 代理模式。下面我将详细解释这两种实现方式的工作原理,并说明在什么情况下会选择使用 CGLIB代理。
一、Spring AOP 的两种代理方式
1. JDK 动态代理
- 原理:JDK 动态代理是基于接口的代理方式。它通过
java.lang.reflect.Proxy类创建代理对象。 - 适用场景:被代理的类必须实现一个或多个接口。
- 工作方式:
- Spring 会为每个需要增强的接口生成一个代理对象。
- 当调用目标方法时,实际调用的是代理对象,而代理对象会调用
InvocationHandler中的invoke()方法。 - 在
invoke()方法中,可以插入前置、后置、异常处理等逻辑。
示例代码:
public interface UserService { void sayHello(); } public class UserServiceImpl implements UserService { @Override public void sayHello() { System.out.println("Hello"); } }public class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强 System.out.println("Before method: " + method.getName()); Object result = method.invoke(target, args); // 后置增强 System.out.println("After method: " + method.getName()); return result; } }使用方式:
UserService userService = (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), new Class[]{UserService.class}, new MyInvocationHandler(new UserServiceImpl()) ); userService.sayHello();
2. CGLIB(Code Generation Library)
- 原理:CGLIB 是一个基于字节码操作的库,通过继承的方式生成目标类的子类,从而实现代理。
- 适用场景:被代理的类没有实现接口时,Spring 会自动选择 CGLIB。
- 工作方式:
- Spring 会使用 CGLIB 生成一个目标类的子类。
- 在子类中重写目标方法,插入切面逻辑(如日志、事务管理等)。
- 调用时实际上是调用这个子类的对象。
示例代码(无需接口):
public class UserService { public void sayHello() { System.out.println("Hello"); } }Spring 会自动使用 CGLIB 对该类进行代理,不需要接口。
二、Spring AOP 如何实现方法拦截?
Spring AOP 使用 动态代理技术 来实现方法拦截,具体流程如下:
- 确定代理类型:根据目标类是否实现接口,决定使用 JDK 动态代理还是 CGLIB。
- 生成代理对象:Spring 根据配置和目标类生成对应的代理对象。
- 执行方法调用:当调用代理对象的方法时,实际上调用的是代理对象内部的逻辑。
- 插入切面逻辑:在方法调用前后插入切面逻辑(如日志、权限检查、事务控制等)。
三、Spring AOP 选择 CGLIB 代理的条件
在以下情况下,Spring AOP 会使用 CGLIB 代理:
-
目标类没有实现任何接口
- 如果被代理的类没有实现接口,那么只能使用 CGLIB 代理。
-
使用
@Aspect注解定义切面- 如果你使用了
@Aspect注解,Spring 会默认使用 CGLIB 代理,因为@Aspect需要对类进行增强,而不是接口。
- 如果你使用了
-
使用
@EnableAspectJAutoProxy配置- 即使目标类实现了接口,如果启用了
@EnableAspectJAutoProxy,Spring 也会优先使用 CGLIB。
- 即使目标类实现了接口,如果启用了
-
使用
@Around等注解- 如果你的切面中使用了
@Around注解,Spring 通常会使用 CGLIB 来确保能够拦截所有方法。
- 如果你的切面中使用了
四、Spring AOP 选择 JDK 动态代理的条件
在以下情况下,Spring AOP 会使用 JDK 动态代理:
-
目标类实现了至少一个接口
- Spring 会优先使用 JDK 动态代理,因为它更轻量、性能更好。
-
显式配置使用 JDK 动态代理
- 可以通过设置
proxy-target-class="false"或者在配置类中设置@EnableAspectJAutoProxy(proxyTargetClass = false)强制使用 JDK 动态代理。
- 可以通过设置
五、总结:Spring AOP 如何实现方法拦截?
Spring AOP 通过以下方式实现方法拦截:
- 根据目标类是否实现接口,选择 JDK 动态代理或 CGLIB 代理。
- 生成代理对象,代理对象封装了原始对象和切面逻辑。
- 调用方法时,实际调用的是代理对象,从而触发切面逻辑。
- 在方法执行前后插入增强逻辑(如日志、事务等)。
六、何时选择 CGLIB 代理?
在以下情况下应选择 CGLIB 代理:
- 目标类没有实现任何接口。
- 使用
@Aspect注解定义切面。 - 需要对非接口方法进行增强(如私有方法、final 方法等)。
- 希望避免接口依赖,直接对类进行增强。
七、代码示例:如何强制使用 CGLIB 代理?
配置类方式:
@Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) public class AppConfig { }XML 配置方式:
<aop:config> <aop:aspectj-autoproxy proxy-target-class="true"/> </aop:config>
八、CGLIB vs JDK 动态代理的区别(对比表)
| 特性 | JDK 动态代理 | CGLIB | |---------------------|-------------------------------|----------------------------------| | 是否依赖接口 | ✅ 必须实现接口 | ❌ 不需要接口 | | 性能 | 更快 | 略慢(字节码操作) | | 支持方法 | 接口方法 | 所有方法(包括私有、final) | | 使用场景 | 有接口的类 | 没有接口的类或需要增强非接口方法 | | 代理方式 | 基于接口的代理 | 基于继承的代理 |
如果你有具体的项目结构或代码片段,我可以进一步帮你分析应该使用哪种代理方式。
解决 无用评论 打赏 举报- 原理:JDK 动态代理是基于接口的代理方式。它通过