Spring AOP 核心的技术之一:动态代理
什么是动态代理?
动态代理,是一种在运行时动态创建代理对象的技术。这个代理对象可以在不修改原始类源代码的情况下,拦截对原始对象方法的调用,并在调用前后插入自定义的逻辑。
关于Spring AOP
Spring AOP 就是动态代理的一个典型应用。当我们定义一个切面 (Aspect) 和切点 (Pointcut) 后,Spring 容器会在启动时或者运行时,为匹配切点的 Bean 创建一个代理对象。
这个代理对象会包装(Wrap)原始的 Bean。当我们调用这个 Bean 的方法时,实际上是先调用了代理对象的方法。代理对象内部会根据 AOP 的配置(比如 @Before
, @Around
, @After
通知),先执行相应的增强逻辑,然后再(或者在 @Around
通知里决定是否)去调用原始 Bean 的目标方法。
Spring 主要使用两种动态代理技术:如果是基于接口的代理,默认使用 JDK 动态代理(需要目标类实现接口);如果是基于类的代理,则使用 CGLIB(通过生成子类的方式)。
关于JDK 动态代理和CGLIB
先通过代码写一个简单的 Demo 来理解他们
** 我们需要一个被代理的目标:**
// 1. 目标接口
public interface UserService {
String addUser(String username);
void deleteUser(String username);
}
// 2. 目标实现类 (这个类可以被 JDK 和 CGLIB 代理)
public class UserServiceImpl implements UserService {
@Override
public String addUser(String username) {
System.out.println("UserServiceImpl [数据库]:添加用户 " + username);
return "添加成功: " + username;
}
@Override
public void deleteUser(String username) {
System.out.println("UserServiceImpl [数据库]:删除用户 " + username);
}
// CGLIB 可以代理非接口方法 (如果类不是final)
public void nonInterfaceMethod() {
System.out.println("UserServiceImpl: 这是一个实现类自己定义的方法。");
}
}
JDK 动态代理:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 3. JDK 代理的 InvocationHandler
class JdkInvocationHandler implements InvocationHandler {
// 目标对象
private final Object target;
public JdkInvocationHandler(Object target) {
this.target = target;
}
/**
* 当通过代理对象调用方法时,这个 invoke 方法会被执行
* @param proxy 代理对象本身 (一般很少使用)
* @param method 被调用的目标方法 (例如 addUser 方法的 Method 对象)
* @param args 调用目标方法时传入的参数 (例如 "张三")
* @return 目标方法的返回值
* @throws Throwable 目标方法执行可能抛出的异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[JDK 代理] === 方法 [" + method.getName() + "] 执行前 ===");
// 通过反射调用目标对象的原始方法
Object result = method.invoke(target, args);
System.out.println("[JDK 代理] === 方法 [" + method.getName() + "] 执行后 ===");
System.out.println("[JDK 代理] 返回结果: " + result); // 注意: deleteUser 没有返回值,这里会是 null
// 可以对结果进行处理再返回
if (result instanceof String) {
return ((String) result).toUpperCase(); // 示例:将返回的字符串转大写
}
return result;
}
}
// 4. JDK 代理测试类
public class JdkProxyDemo {
public static void main(String[] args) {
// 1. 创建目标对象实例
UserService target = new UserServiceImpl();
System.out.println("目标对象类名: " + target.getClass().getName()); // 输出原始类名
// 2. 创建 InvocationHandler 实例
InvocationHandler handler = new JdkInvocationHandler(target);
// 3. 使用 Proxy.newProxyInstance 创建代理对象
// 参数1: 目标对象的类加载器
// 参数2: 目标对象实现的接口数组 (代理对象会实现这些接口)
// 参数3: InvocationHandler 实例
UserService proxyInstance = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), // 关键:必须基于接口
handler
);
System.out.println("代理对象类名: " + proxyInstance.getClass().getName()); // 输出代理类名,类似 com.sun.proxy.$Proxy0
// 4. 通过代理对象调用方法
System.out.println("\n------ 调用 addUser ------");
String addUserResult = proxyInstance.addUser("张三");
System.out.println("最终调用者收到的结果: " + addUserResult);
System.out.println("\n------ 调用 deleteUser ------");
proxyInstance.deleteUser("李四");
// 注意:JDK 代理无法直接调用目标实现类中定义的非接口方法
// 下面这行会报错,因为代理对象只实现了 UserService 接口
proxyInstance.nonInterfaceMethod(); // 编译错误
// 如果非要调用,需要强转成原始类型,但这样就失去了代理的意义
((UserServiceImpl)proxyInstance).nonInterfaceMethod(); // 运行时会抛 ClassCastException,因为代理对象不是 UserServiceImpl 类型
}
}
CGLIB 动态代理:
CGLIB 需要引入依赖。如果是 Maven 项目,在 pom.xml
中添加:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
CGLIB 需要一个 MethodInterceptor
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 5. CGLIB 的 MethodInterceptor
class CglibMethodInterceptor implements MethodInterceptor {
// CGLIB 不需要显式持有目标对象,因为它是通过创建子类来实现的
/**
* 拦截 CGLIB 生成的代理类(子类)的方法调用
* @param obj CGLIB 生成的代理对象 (子类实例)
* @param method 被拦截的父类方法 (例如 UserServiceImpl 的 addUser 方法)
* @param args 调用方法的参数
* @param methodProxy 用于调用父类原始方法的代理,效率比反射高
* @return 原始方法的返回值
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("[CGLIB 代理] === 方法 [" + method.getName() + "] 执行前 ===");
// 通过 MethodProxy 调用原始父类的方法
// 注意:这里是 invokeSuper,不是 invoke
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("[CGLIB 代理] === 方法 [" + method.getName() + "] 执行后 ===");
System.out.println("[CGLIB 代理] 返回结果: " + result);
// 同样可以处理结果
if (result instanceof String) {
return ((String) result).toUpperCase() + " (From CGLIB)"; // 示例:加点区别
}
return result;
}
}
// 6. CGLIB 代理测试类
public class CglibProxyDemo {
public static void main(String[] args) {
// 1. 创建 CGLIB 的 Enhancer 对象
Enhancer enhancer = new Enhancer();
// 2. 设置需要被代理的父类
enhancer.setSuperclass(UserServiceImpl.class); // 关键:基于类
// 3. 设置回调拦截器
enhancer.setCallback(new CglibMethodInterceptor());
// 4. 创建代理对象 (实际上是 UserServiceImpl 的子类实例)
UserServiceImpl proxyInstance = (UserServiceImpl) enhancer.create();
System.out.println("代理对象类名: " + proxyInstance.getClass().getName()); // 输出代理类名,类似 xxx.UserServiceImpl$$EnhancerByCGLIB$$...
// 5. 通过代理对象调用方法
System.out.println("\n------ 调用 addUser ------");
String addUserResult = proxyInstance.addUser("王五");
System.out.println("最终调用者收到的结果: " + addUserResult);
System.out.println("\n------ 调用 deleteUser ------");
proxyInstance.deleteUser("赵六");
// CGLIB 可以代理父类中非 final 的 public/protected 方法,即使它不是接口方法
System.out.println("\n------ 调用 nonInterfaceMethod ------");
proxyInstance.nonInterfaceMethod();
// 注意:如果 UserServiceImpl 是 final 类,或者 addUser 是 final 方法,CGLIB 会失败
}
}
说下 CGLIB 动态代理与 JDK 动态代理的区别
特性 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|
实现原理 | 基于接口实现 | 基于继承目标类 (创建子类) |
依赖 | Java SDK 自带,无需额外依赖 | 需要引入第三方 cglib 库 |
代理对象类型 | 实现目标对象相同接口的新类实例 | 目标对象的子类实例 |
目标类要求 | 目标类必须实现至少一个接口 | 目标类无需实现接口,但不能是 final 类 |
代理方法范围 | 只能代理接口中定义的方法 | 可以代理目标类中所有非 final 的方法 |
拦截器 | java.lang.reflect.InvocationHandler | net.sf.cglib.proxy.MethodInterceptor |
调用原始方法 | 使用反射 Method.invoke(target, args) | 使用 MethodProxy.invokeSuper(proxy, args) |
性能 (历史观点) | 创建代理稍慢,方法调用稍快(但反射调用有开销) | 销) 创建代理稍快,方法调用稍慢 (但 invokeSuper 通常比反射快) |
性能 (现代观点) | 差异通常很小,可以忽略不计。 | invokeSuper 比反射invoke 通常性能更好。 |
Spring AOP | 目标类实现接口时默认使用 | 目标类未实现接口时使用,或强制配置时使用 |
简单的来说就是:
- JDK 代理和被代理的目标类是兄弟关系(都实现了同一个接口)。
- CGLIB 代理和被代理的目标类是父子关系(代理类继承了目标类)。
最后
如果目标类实现了接口 Spring AOP 默认使用 JDK 动态代理。
原因: 这是 Java 标准库提供的代理方式,并且通常认为基于接口的代理是更优的面向对象实践。Spring 会创建一个实现了目标类所实现的所有接口的新代理类。这个代理类持有对原始目标对象的引用,并通过 InvocationHandler 机制将增强逻辑(AOP Advice)织入。
结果: 你得到的代理 Bean 可以被赋值给它所实现的任何接口类型,但不能直接赋值给原始的目标实现类类型(除非强制类型转换,但通常不推荐,且可能失败)。
如果目标类没有实现任何接口 那么只能使用 CGLIB 代理。
原因: JDK 动态代理要求必须有接口,既然目标类没有实现接口,JDK 代理就无法工作。Spring 别无选择,只能借助 CGLIB 库。CGLIB 通过创建目标类的子类来作为代理。它会覆写父类(目标类)中的非 final 方法,并在这些覆写的方法中加入增强逻辑,然后调用父类的原始方法。
结果: 你得到的代理 Bean 是目标实现类的一个子类。因此,它可以被赋值给原始的目标实现类类型。
Spring 提供了一个配置选项,允许你强制 Spring AOP 使用 CGLIB 代理,即使目标类实现了接口。这个配置就是 proxy-target-class 属性。
可以通过配置更改默认动态代理的实现
Spring 提供了一个配置选项,允许你强制 Spring AOP 使用 CGLIB 代理,即使目标类实现了接口。这个配置就是 proxy-target-class
属性。
proxy-target-class = false
(默认值 - 在非 Spring Boot 或较早的 Spring Boot 版本中):- 遵循上述的基本规则:实现接口用 JDK,没实现接口用 CGLIB。
proxy-target-class = true:
- 强制 Spring AOP 统一使用 CGLIB 代理,无论目标类是否实现了接口。
- 只要目标类不是 final 的,并且需要被代理的方法不是 final 的,Spring 就会为它创建基于类的 CGLIB 代理。
实例:
# application.yml
spring:
aop:
proxy-target-class: true
随便聊聊
除了 AOP,动态代理在 Spring 框架的其他模块中也有广泛应用。比如,我们常用的 @Transactional
注解,它的事务开启、提交、回滚逻辑就是通过动态代理织入到业务方法调用的前后。还有像 Spring Cloud Feign,它能让我们像调用本地接口一样调用远程 HTTP 服务,其底层也是为我们定义的 Feign 接口生成了动态代理实现类,由代理类负责网络通信和序列化。
注意:
CGLIB 无法代理目标类中 final 或 private 的方法(因为 final 方法不能被子类覆写,private 方法对子类不可见)。这也是@Transactional
如果应用于非 public 方法时会导致事务失效的一个原因