Spring AOP实现原理(动态代理 vs CGLib)
1. 是什么?
Spring AOP(面向切面编程) 是一种编程思想,允许你在不修改原有代码的情况下,动态地给某些方法添加额外功能(如日志、事务、权限控制等)。
动态代理 和 CGLib 是 Spring AOP 的两种底层实现方式:
- JDK 动态代理:基于接口,运行时生成代理类(
Proxy.newProxyInstance())。 - CGLib 代理:基于继承,运行时生成目标类的子类(
Enhancer.create())。
举例:
- 你有一个
UserService类,里面有个saveUser()方法。 - 你想在
saveUser()执行前后自动打印日志,但不想改UserService的代码。 - Spring AOP 会动态生成一个代理类,在调用
saveUser()时,先执行日志逻辑,再调用真实方法。
2. 为什么要用?
(1)解决代码重复问题
- 比如多个方法都需要记录日志、事务管理、权限校验,如果每个方法都写一遍,代码会变得臃肿。
- AOP 可以统一处理这些横切关注点,让业务代码更干净。
(2)解耦
- 业务代码(如
UserService.saveUser())和增强逻辑(如日志、事务)分离。 - 修改增强逻辑时,不影响业务代码。
(3)灵活扩展
- 可以随时添加或移除功能(如:开发阶段加日志,生产环境去掉)。
3. 怎么学?
(1)先理解 AOP 核心概念
| 概念 | 解释 | 示例 |
|---|---|---|
| 切面(Aspect) | 封装增强逻辑的类 | @Aspect 注解的类 |
| 通知(Advice) | 增强的具体逻辑 | @Before(方法前执行)、@After(方法后执行) |
| 切点(Pointcut) | 定义哪些方法需要增强 | @Pointcut("execution(* com.example.service.*.*(..))") |
| 连接点(JoinPoint) | 目标方法(如 saveUser()) | 可以在通知里获取方法名、参数等 |
一、三大核心概念关系图
二、切点(Pointcut)详解
1. 核心作用
精确定位需要增强的方法,如:“所有Service层的public方法”
2. 表达式语法
// 常用表达式示例
@Pointcut("execution(public * com.example.service.*.*(..))") // 匹配service包下所有public方法
@Pointcut("@annotation(com.example.annotation.Log)") // 匹配带@Log注解的方法
@Pointcut("within(@org.springframework.stereotype.Service *)") // 匹配Service注解的类
3. 组合切点
@Pointcut("execution(* com.example..*(..)) && args(String)")
public void stringParams() {}
@Pointcut("execution(* save*(..)) || execution(* update*(..))")
public void writeOperations() {}
三、通知(Advice)类型
| 通知类型 | 注解 | 执行时机 | 典型应用场景 |
|---|---|---|---|
| 前置通知 | @Before | 方法执行前 | 权限校验、参数校验 |
| 后置通知 | @After | 方法执行后(无论是否异常) | 资源清理、日志记录 |
| 返回通知 | @AfterReturning | 方法正常返回后 | 结果处理、成功日志 |
| 异常通知 | @AfterThrowing | 方法抛出异常时 | 异常处理、错误监控 |
| 环绕通知 | @Around | 方法执行前后(完全控制流程) | 性能监控、事务管理 |
环绕通知示例:
@Around("serviceMethods()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed(); // 控制目标方法执行
log.info("执行成功, 耗时: {}ms", System.currentTimeMillis()-start);
return result;
} catch (Exception e) {
log.error("执行异常", e);
throw e;
}
}
四、切面(Aspect)实现
@Aspect
@Component
@Slf4j
public class BusinessMonitorAspect {
// 定义切点(可复用)
@Pointcut("execution(* com.example..service.*.*(..))")
public void serviceLayer() {}
// 前置通知
@Before("serviceLayer()")
public void logMethodStart(JoinPoint jp) {
log.debug("开始执行: {}", jp.getSignature());
}
// 返回通知(可获取返回值)
@AfterReturning(
pointcut = "serviceLayer()",
returning = "result")
public void logSuccess(JoinPoint jp, Object result) {
log.info("执行成功, 返回: {}", result);
}
// 异常通知(可捕获特定异常)
@AfterThrowing(
pointcut = "serviceLayer()",
throwing = "ex")
public void logFailure(JoinPoint jp, NullPointerException ex) {
log.error("空指针异常", ex);
}
}
五、Spring AOP执行顺序
完整调用链:
顺序控制注解:
@Aspect
@Order(1) // 数字越小优先级越高
public class HighPriorityAspect {
// 会先于@Order(2)的切面执行
}
六、性能优化方法
-
切点表达式优化:
// 低效写法(每次执行都解析) @Around("execution(* com.example..*(..)) && args(param)") // 高效写法(编译期优化) @Pointcut("execution(* com.example..*(..))") public void generalPointcut() {} @Around("generalPointcut() && args(param)") -
避免在切面中做IO操作:
// 错误示范 @Around("serviceMethods()") public Object badPractice(ProceedingJoinPoint pjp) throws Throwable { // 同步写入日志文件会严重影响性能! Files.write(Paths.get("log.txt"), "调用记录".getBytes()); return pjp.proceed(); } -
使用条件切面:
@Around("serviceMethods()") public Object conditionalAdvice(ProceedingJoinPoint pjp) throws Throwable { if (log.isDebugEnabled()) { // 根据日志级别控制 long start = System.nanoTime(); Object result = pjp.proceed(); log.debug("执行耗时: {}ns", System.nanoTime()-start); return result; } return pjp.proceed(); }
(2)动手实现
- JDK 动态代理(适合接口):
public class JdkProxyDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(p, method, args1) -> {
System.out.println("Before: " + method.getName());
Object result = method.invoke(target, args1);
System.out.println("After: " + method.getName());
return result;
});
proxy.saveUser(); // 调用代理方法
}
}
CGLib 代理(适合类):
public class CglibProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> {
System.out.println("Before: " + method.getName());
Object result = proxy.invokeSuper(obj, args1);
System.out.println("After: " + method.getName());
return result;
});
UserService proxy = (UserService) enhancer.create();
proxy.saveUser(); // 调用代理方法
}
}
(3)学习 Spring AOP 实战
@Aspect
@Component
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))") // 切点表达式
public void beforeLog(JoinPoint joinPoint) {
System.out.println("方法执行前: " + joinPoint.getSignature().getName());
}
}
4. 怎么使用?
(1)Spring Boot 启用 AOP
@SpringBootApplication
@EnableAspectJAutoProxy // 开启 AOP
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
(2)定义切面
@Aspect
@Component
public class TransactionAspect {
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("开启事务");
try {
Object result = pjp.proceed(); // 执行目标方法
System.out.println("提交事务");
return result;
} catch (Exception e) {
System.out.println("回滚事务");
throw e;
}
}
}
(3)测试
@Service
public class UserService {
@Transactional // 会被 AOP 代理增强
public void saveUser() {
System.out.println("保存用户...");
}
}
运行结果:
开启事务
保存用户...
提交事务
5. JDK动态代理 vs CGLib 深度对比
(1)核心区别
| 对比维度 | JDK动态代理 | CGLib |
|---|---|---|
| 实现原理 | 基于接口(反射调用) | 基于子类继承(字节码增强) |
| 代理目标 | 必须实现至少一个接口 | 可以代理普通类(final类/方法除外) |
| 生成方式 | Proxy.newProxyInstance() | Enhancer.create() |
| 性能特点 | 调用速度快(约快15%-20%) | 生成速度快(首次加载后缓存字节码) |
| 内存占用 | 较小(仅生成接口代理) | 较大(生成子类包含所有方法) |
| Spring默认选择 | 有接口时优先使用 | proxyTargetClass=true时强制使用 |
(2)性能实测数据(百万次调用)
// 测试代码片段
public class ProxyBenchmark {
public static void main(String[] args) {
// JDK代理测试
Interface jdkProxy = createJdkProxy();
long jdkStart = System.currentTimeMillis();
for (int i = 0; i < 1_000_000; i++) {
jdkProxy.doSomething();
}
System.out.println("JDK 耗时: " + (System.currentTimeMillis() - jdkStart) + "ms");
// CGLib代理测试
TargetClass cglibProxy = createCglibProxy();
long cglibStart = System.currentTimeMillis();
for (int i = 0; i < 1_000_000; i++) {
cglibProxy.doSomething();
}
System.out.println("CGLib 耗时: " + (System.currentTimeMillis() - cglibStart) + "ms");
}
}
典型测试结果:
- JDK代理调用:约120-150ms
- CGLib代理调用:约140-180ms
- 直接调用(无代理):约80-100ms
(3)底层实现原理
1. JDK动态代理核心流程
// 生成的代理类伪代码
public final class $Proxy0 extends Proxy implements TargetInterface {
public $Proxy0(InvocationHandler h) {
super(h);
}
public void doSomething() {
// 通过InvocationHandler转发调用
super.h.invoke(this,
TargetInterface.class.getMethod("doSomething"),
null);
}
}
2. CGLib代理核心流程
// 生成的子类伪代码
public class TargetClass$$EnhancerByCGLIB extends TargetClass {
private MethodInterceptor interceptor;
public void doSomething() {
// 通过MethodInterceptor拦截
interceptor.intercept(this,
getClass().getMethod("doSomething"),
new Object[0],
MethodProxy.create(...));
}
}
3.Spring的选择策略
(4)生产环境选型建议
优先使用JDK动态代理:
- 服务间通过接口调用(RPC/Feign)
- 需要代理的类已实现接口
- 对内存敏感的场景
优先使用CGLib:
- 需要代理具体类(如第三方库)
- 代理final方法外的所有方法
- 项目已大量使用类代理
5. 组成部分
| 组成部分 | 作用 | 示例 |
|---|---|---|
| JDK 动态代理 | 基于接口生成代理 | Proxy.newProxyInstance() |
| CGLib 代理 | 基于子类生成代理 | Enhancer.create() |
| @Aspect | 定义切面 | @Aspect public class LogAspect |
| @Before/@After | 定义通知 | @Before("execution(* com.example..*(..))") |
| @Pointcut | 定义切点 | @Pointcut("within(@org.springframework.stereotype.Service *)") |
| @EnableAspectJAutoProxy | 启用 AOP | @EnableAspectJAutoProxy(proxyTargetClass=true) |
6.手动实现JDK动态代理(InvocationHandler接口)
(1)JDK 动态代理核心机制
- 依赖接口:必须通过接口创建代理
- 核心类
java.lang.reflect.Proxy:生成代理类java.lang.reflect.InvocationHandler:定义代理逻辑
- 执行流程:
(2)完整实现代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyDemo {
// 1. 定义业务接口
interface UserService {
void saveUser(String username);
String getUserId(int id);
}
// 2. 实现真实对象
static class UserServiceImpl implements UserService {
@Override
public void saveUser(String username) {
System.out.println("真实保存用户: " + username);
}
@Override
public String getUserId(int id) {
return "用户ID-" + id;
}
}
// 3. 实现调用处理器
static class MyInvocationHandler implements InvocationHandler {
private final Object target; // 真实对象
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("【代理】准备执行: " + method.getName());
// 反射调用真实方法
Object result = method.invoke(target, args);
// 后置增强
System.out.println("【代理】执行完成: " + method.getName());
return result;
}
}
public static void main(String[] args) {
// 4. 创建真实对象
UserService realService = new UserServiceImpl();
// 5. 创建代理对象
UserService proxyInstance = (UserService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(), // 类加载器
realService.getClass().getInterfaces(), // 接口数组
new MyInvocationHandler(realService) // 调用处理器
);
// 6. 测试代理调用
proxyInstance.saveUser("张三");
System.out.println(proxyInstance.getUserId(1001));
}
}
(3)关键代码逐行解析
1. 创建代理对象(核心方法)
Proxy.newProxyInstance(
ClassLoader loader, // 1. 使用真实对象的类加载器
Class<?>[] interfaces, // 2. 代理要实现的接口
InvocationHandler h // 3. 自定义的调用处理器
)
2. InvocationHandler 接口方法
public Object invoke(
Object proxy, // 代理对象本身(一般不用)
Method method, // 当前调用的方法对象
Object[] args // 方法参数
) throws Throwable {
// 在这里编写增强逻辑
}
3. 反射调用真实方法
method.invoke(target, args);
// target: 真实对象
// args: 方法参数
(4)执行结果分析
【代理】准备执行: saveUser
真实保存用户: 张三
【代理】执行完成: saveUser
【代理】准备执行: getUserId
【代理】执行完成: getUserId
用户ID-1001
总结
- 是什么?
- AOP 是一种编程思想,动态代理和 CGLib 是实现方式。
- 为什么要用?
- 解决代码重复、解耦、灵活扩展。
- 怎么学?
- 理解核心概念(切面、通知、切点),动手写代理。
- 怎么用?
@EnableAspectJAutoProxy+@Aspect+@Before/@After。
- 组成部分
- JDK 动态代理(接口)、CGLib(类)、
@Aspect、@Pointcut。
- JDK 动态代理(接口)、CGLib(类)、
适用场景:
- 日志记录
- 事务管理(
@Transactional) - 权限校验
- 性能监控(统计方法耗时)
一句话理解 AOP:
“在不改代码的情况下,动态给方法加功能” 🚀
&spm=1001.2101.3001.5002&articleId=147101317&d=1&t=3&u=d366927893fa4f0883ad56ddf45417e6)
1万+

被折叠的 条评论
为什么被折叠?



