Appearance
Spring AOP动态代理
要理解Spring AOP的动态代理,我们需要从「AOP的核心问题」出发——如何在不修改目标对象代码的情况下,对其方法进行增强(比如日志、事务、权限校验)?动态代理正是解决这个问题的底层技术。
一、AOP的核心概念
在讲动态代理前,先明确AOP的几个关键术语(避免后续混淆):
- 目标对象(Target):需要被增强的原始对象(比如业务层的
UserService)。 - 代理对象(Proxy):动态生成的、包裹目标对象的对象,实际对外提供服务的是代理对象。
- 连接点(JoinPoint):目标对象中可以被增强的点(比如所有方法的执行)。
- 切点(Pointcut):具体选择哪些连接点(比如只增强
UserService中以save开头的方法)。 - 通知(Advice):增强的逻辑(比如前置日志、后置事务提交)。
- 切面(Aspect):切点 + 通知的组合(比如「对
save*方法添加日志」就是一个切面)。
二、动态代理的本质
动态代理的核心是在运行时生成一个「代理对象」,这个代理对象会:
- 实现与目标对象相同的接口(JDK代理)或继承目标对象(CGLIB代理);
- 拦截目标对象的方法调用;
- 在方法执行前后插入通知逻辑(比如日志、事务);
- 最终调用目标对象的原始方法。
Spring AOP支持两种动态代理实现:JDK动态代理(JDK原生)和CGLIB动态代理(第三方库,Spring已集成)。
三、JDK动态代理(基于接口)
JDK动态代理是Java原生支持的代理方式,要求目标对象必须实现接口。
1. 核心类/接口
java.lang.reflect.Proxy:用于生成代理对象的工具类,核心方法是newProxyInstance()。java.lang.reflect.InvocationHandler:代理逻辑的回调接口,需要实现invoke()方法(所有代理对象的方法调用都会走到这里)。
2. 工作原理
- 目标类实现一个接口(比如
UserService接口); - 创建
InvocationHandler实现类,在invoke()方法中编写增强逻辑; - 通过
Proxy.newProxyInstance()生成代理对象(代理对象实现了目标接口); - 调用代理对象的方法时,会触发
InvocationHandler.invoke(),执行增强逻辑后调用目标对象的原始方法。
3. 代码示例
假设我们有一个「用户服务」,需要在保存用户时添加日志:
步骤1:定义接口和目标类
java
// 1. 目标接口
public interface UserService {
void saveUser(String username);
}
// 2. 目标类(实现接口)
public class UserServiceImpl implements UserService {
@Override
public void saveUser(String username) {
System.out.println("目标方法执行:保存用户 [" + username + "]");
}
}步骤2:实现InvocationHandler(代理逻辑)
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 代理逻辑处理器:负责插入增强代码
public class LogInvocationHandler implements InvocationHandler {
// 目标对象(被代理的原始对象)
private Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
/**
* 所有代理对象的方法调用都会触发这个方法
* @param proxy 代理对象本身(很少用)
* @param method 目标方法(被调用的方法)
* @param args 目标方法的参数
* @return 目标方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 前置增强(日志)
System.out.println("前置通知:准备调用 [" + method.getName() + "] 方法,参数:" + args[0]);
// 2. 调用目标对象的原始方法
Object result = method.invoke(target, args);
// 3. 后置增强(日志)
System.out.println("后置通知:[" + method.getName() + "] 方法执行完成");
return result;
}
}步骤3:生成代理对象并测试
java
import java.lang.reflect.Proxy;
public class JdkProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象
UserService target = new UserServiceImpl();
// 2. 创建InvocationHandler(传入目标对象)
InvocationHandler handler = new LogInvocationHandler(target);
// 3. 生成代理对象(三个参数:类加载器、目标接口数组、InvocationHandler)
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器(与目标对象一致)
target.getClass().getInterfaces(), // 目标对象实现的接口(代理对象会实现这些接口)
handler // 代理逻辑处理器
);
// 4. 调用代理对象的方法(会触发invoke())
proxy.saveUser("张三");
}
}输出结果
前置通知:准备调用 [saveUser] 方法,参数:张三
目标方法执行:保存用户 [张三]
后置通知:[saveUser] 方法执行完成四、CGLIB动态代理(基于继承)
如果目标类没有实现接口,JDK动态代理就无法使用,这时需要用CGLIB(Code Generation Library)。CGLIB是一个第三方字节码生成库,通过继承目标类生成代理类(代理类是目标类的子类)。
1. 核心类/接口
org.springframework.cglib.proxy.Enhancer:CGLIB的核心类,用于生成代理对象。org.springframework.cglib.proxy.MethodInterceptor:方法拦截器接口,类似JDK的InvocationHandler,需要实现intercept()方法。
2. 工作原理
- 目标类不需要实现接口(比如
OrderService); - 创建
MethodInterceptor实现类,在intercept()方法中编写增强逻辑; - 通过
Enhancer设置父类(目标类)和回调(MethodInterceptor),生成代理对象(代理类是目标类的子类); - 调用代理对象的方法时,会触发
MethodInterceptor.intercept(),执行增强逻辑后调用目标对象的原始方法。
3. 代码示例
假设我们有一个「订单服务」,没有实现接口,需要添加日志:
步骤1:定义目标类(无接口)
java
// 目标类(无接口)
public class OrderService {
public void createOrder(String orderId) {
System.out.println("目标方法执行:创建订单 [" + orderId + "]");
}
}步骤2:实现MethodInterceptor(代理逻辑)
java
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 方法拦截器:负责插入增强代码
public class LogMethodInterceptor implements MethodInterceptor {
// 目标对象(被代理的原始对象)
private Object target;
public LogMethodInterceptor(Object target) {
this.target = target;
}
/**
* 所有代理对象的方法调用都会触发这个方法
* @param proxy 代理对象本身
* @param method 目标方法(被调用的方法)
* @param args 目标方法的参数
* @param methodProxy 方法代理(用于调用目标方法,比反射更快)
* @return 目标方法的返回值
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 1. 前置增强(日志)
System.out.println("前置通知:准备调用 [" + method.getName() + "] 方法,参数:" + args[0]);
// 2. 调用目标对象的原始方法(两种方式:反射或methodProxy)
// 方式1:反射(与JDK类似)
// Object result = method.invoke(target, args);
// 方式2:methodProxy(CGLIB推荐,性能更好)
Object result = methodProxy.invoke(target, args);
// 3. 后置增强(日志)
System.out.println("后置通知:[" + method.getName() + "] 方法执行完成");
return result;
}
}步骤3:生成代理对象并测试
java
import org.springframework.cglib.proxy.Enhancer;
public class CglibProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象
OrderService target = new OrderService();
// 2. 创建MethodInterceptor(传入目标对象)
LogMethodInterceptor interceptor = new LogMethodInterceptor(target);
// 3. 生成代理对象(Enhancer是CGLIB的核心类)
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置父类(目标类)
enhancer.setCallback(interceptor); // 设置回调(代理逻辑)
OrderService proxy = (OrderService) enhancer.create(); // 生成代理对象
// 4. 调用代理对象的方法(会触发intercept())
proxy.createOrder("OD123456");
}
}输出结果
前置通知:准备调用 [createOrder] 方法,参数:OD123456
目标方法执行:创建订单 [OD123456]
后置通知:[createOrder] 方法执行完成五、JDK vs CGLIB:关键区别
| 维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 依赖 | JDK原生(无需额外依赖) | 需要CGLIB库(Spring已集成) |
| 目标类要求 | 必须实现接口 | 无需实现接口,但不能是final类(无法继承)、方法不能是final(无法重写) |
| 代理方式 | 实现目标接口(代理类是接口的实现) | 继承目标类(代理类是目标类的子类) |
| 性能 | JDK 8+优化后,性能优于CGLIB | 生成代理类时比JDK慢(字节码生成),但运行时性能与JDK接近 |
| Spring默认选择 | 目标类实现接口时,优先用JDK | 目标类无接口时,用CGLIB |
六、Spring AOP如何使用动态代理?
Spring AOP自动选择代理方式:
- 如果目标类实现了接口:默认用JDK动态代理;
- 如果目标类没有实现接口:用CGLIB;
- 可以通过配置强制使用CGLIB(比如
spring.aop.proxy-target-class=true,或在@EnableAspectJAutoProxy(proxyTargetClass = true)中设置)。
Spring AOP的简化开发
开发者不需要手动写Proxy或Enhancer的代码,只需定义切面(@Aspect)和通知(@Before/@After等),Spring会自动生成代理对象。
示例:Spring AOP的日志切面
java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// 1. 标记为切面(@Aspect)+ 组件(@Component,让Spring扫描到)
@Aspect
@Component
public class LogAspect {
// 2. 定义切点:匹配com.example.service包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePointcut() {}
// 3. 前置通知:切点执行前触发
@Before("servicePointcut()")
public void beforeAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("前置通知:方法[" + methodName + "],参数:" + args[0]);
}
// 4. 后置通知:切点执行后触发(无论是否异常)
@After("servicePointcut()")
public void afterAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("后置通知:方法[" + methodName + "]执行完成");
}
}业务类(无需修改)
java
import org.springframework.stereotype.Service;
@Service
public class UserService { // 即使没有实现接口,Spring也会用CGLIB代理
public void saveUser(String username) {
System.out.println("保存用户:" + username);
}
}测试类
java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy // 开启AOP自动代理
@ComponentScan("com.example") // 扫描组件
public class SpringAopTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringAopTest.class);
UserService userService = context.getBean(UserService.class);
userService.saveUser("李四"); // 调用代理对象的方法,触发切面
}
}输出结果
前置通知:方法[saveUser],参数:李四
保存用户:李四
后置通知:方法[saveUser]执行完成七、注意事项
this调用的问题:
如果目标类内部用this调用自己的方法(比如UserService的saveUser()调用this.updateUser()),this是目标对象本身,不会经过代理对象,所以切面通知不会生效。
解决方法:用AopContext.currentProxy()获取代理对象,再调用方法:java// 错误方式(this是目标对象) // this.updateUser(username); // 正确方式(获取代理对象) ((UserService) AopContext.currentProxy()).updateUser(username);需要开启
exposeProxy = true(在@EnableAspectJAutoProxy(exposeProxy = true)中设置)。final类/方法的问题:
CGLIB无法代理final类(因为无法继承),也无法代理final方法(因为无法重写)。如果目标类是final,Spring会抛出异常。代理对象的类型:
JDK代理的对象是接口类型(比如UserService),不能强制转换为目标类(UserServiceImpl);CGLIB代理的对象是目标类的子类,可以强制转换为目标类。
八、总结
- Spring AOP的底层是动态代理,通过运行时生成代理对象,实现对目标方法的增强。
- JDK动态代理基于接口,CGLIB基于继承,Spring自动选择。
- 开发者只需关注切面和通知的定义,无需手动处理代理逻辑。
动态代理是Spring AOP的「基石」,理解它能帮你更深刻地掌握AOP的工作原理,解决实际开发中的代理相关问题(比如this调用不生效、final方法无法增强等)。
