Spring AOP 全面详解
一、AOP 概述(什么是 AOP)
AOP(Aspect Oriented Programming,面向切面编程)是一种软件开发思想,目的是让开发者能够将程序中的横切关注点(cross-cutting concerns)从核心业务逻辑中抽离出来,使代码更加简洁、可复用、可维护。
横切关注点是什么?
横切关注点不是业务本身,但却伴随业务逻辑:
- 日志记录(Log)
- 安全认证(Authentication)
- 权限校验(Authorization)
- 缓存(Cache)
- 事务控制(Transaction)
- 监控(Metrics)
- 异常处理(Exception Handling)
这些逻辑往往会散落在业务方法的前后,导致:
- 代码重复
- 难以维护
- 逻辑耦合严重
AOP 的目标是:
- 将横切逻辑集中管理
- 不修改原代码的前提下增强功能(OOP 难实现)
- 提高模块化能力
在 Spring 中,AOP 被大量用于:
- 事务管理(@Transactional)
- 日志增强
- 权限控制
- 缓存 @Cacheable
- 注解驱动开发(自定义注解 + AOP)
二、AOP 与 OOP 区别
| 思想 | 描述 |
|---|---|
| OOP(面向对象) | 关注业务模型的抽象与封装,如用户、订单、购物车。 |
| AOP(面向切面) | 关注跨业务的系统性功能,动态插入方法执行前后。 |
两者互补而非替代关系。
三、Spring AOP 的底层原理
Spring AOP 采用动态代理机制实现,而不是修改字节码(AspectJ 才修改字节码)。
核心结论:
Spring AOP 只支持 方法级别增强(Method-level AOP)。
Spring AOP 动态代理实现方式
| 类型 | 使用场景 | 底层机制 |
|---|---|---|
| JDK 动态代理 | 目标对象有接口 | 通过接口生成代理对象 |
| CGLIB 动态代理 | 目标类无接口 | 通过继承 + 覆写方法生成代理对象 |
Spring 5.0 后默认优先选择:
- 若类实现了接口 → JDK 动态代理
- 否则 → CGLIB
Spring Boot 默认开启 CGLIB 代理:
spring.aop.proxy-target-class=true(默认开启,为兼容 @Transactional 等)
四、AOP 的核心概念(重点记忆)
Spring AOP 中有六个必须掌握的术语:
1. JoinPoint(连接点)
能被 AOP 拦截的方法执行点。
Spring AOP 仅支持:
- 方法执行(Method Execution)
2. Pointcut(切点)
决定哪些 JoinPoint 会被拦截。
常用表达式:
java
execution(* com.example.service.*.*(..))
3. Advice(通知)
切面在特定时机执行的增强逻辑。
类型:
- 前置通知(@Before)
- 后置通知(@After)
- 返回通知(@AfterReturning)
- 异常通知(@AfterThrowing)
- 环绕通知(@Around)------最强,能控制方法执行
4. Aspect(切面)
切点 + 通知的组合体。
使用 @Aspect 注解声明。
5. Target(目标对象)
被增强的对象,即业务类。
6. Proxy(代理对象)
实际运行的是代理对象,而不是目标对象。
理解关系图:
Aspect = Pointcut + Advice
Pointcut → 定位方法
Advice → 定义增强逻辑
Target → 原始对象
Proxy → 被增强后的对象
JoinPoint→ 方法执行时刻(可被增强)
五、AOP 注解开发核心示例
下面给出完整的 Spring AOP 注解方式示例。
1. 引入依赖(Spring Boot 自动包含)
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 定义切面类
java
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePointcut() {}
@Before("servicePointcut()")
public void before(JoinPoint joinPoint) {
System.out.println("方法执行前:" + joinPoint.getSignature());
}
@After("servicePointcut()")
public void after() {
System.out.println("方法执行后");
}
@AfterReturning(value = "servicePointcut()", returning = "result")
public void afterReturning(Object result) {
System.out.println("返回结果:" + result);
}
@AfterThrowing(value = "servicePointcut()", throwing = "e")
public void afterThrowing(Exception e) {
System.out.println("发生异常:" + e.getMessage());
}
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前");
Object result = pjp.proceed();
System.out.println("环绕后");
return result;
}
}
六、切点表达式详解(execution 语法)
execution 表达式结构
execution(modifiers-pattern? return-type-pattern
declaring-type-pattern? name-pattern(param-pattern)
throws-pattern?)
常用写法(必须记住)
1. 匹配某包下所有方法:
java
execution(* com.example.service.*.*(..))
2. 匹配某类所有方法
java
execution(* com.example.service.UserService.*(..))
3. 匹配带注解的方法
java
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
七、AOP 中 @Around 的深度讲解(核心)
环绕通知是最强的 Advice,可以:
- 控制方法是否执行
- 修改参数
- 修改返回值
- 捕获异常
- 统计耗时
- 事务控制(@Transactional 就是基于环绕 + 动态代理)
示例:
java
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("---------进入方法---------");
Object result = pjp.proceed(); // 执行目标方法
long time = System.currentTimeMillis() - start;
System.out.println("---------执行耗时:" + time + "ms---------");
return result;
}
八、AspectJ 与 Spring AOP 区别
1. Spring AOP
- 基于代理
- 方法级别
- 运行时增强
- 不修改 class 字节码
2. AspectJ
- 编译时增强(compile-time weaving)
- 类级别、字段级别、构造器级别增强
- 功能远强于 Spring AOP
- 需要 AJ 编译器
- 企业开发一般使用 Spring AOP,不使用 AspectJ 编译器
九、Spring AOP 工作流程(面试常问)
核心流程(JDK 动态代理举例):
- IOC 容器启动,扫描到 @Aspect
- Spring 解析切面,生成切点表达式
- 使用 Advisor、MethodMatcher 组合增强器
- 在创建 Bean 时判断是否匹配切点
- 如果匹配,则创建代理对象(Proxy)
- 调用业务方法时,会先进入代理逻辑,再执行通知,最后执行目标方法
示意图:
Client
↓
Proxy(增强逻辑)
↓
Target(真实业务对象)
十、自定义注解 + AOP(高级开发必会)
例如自定义一个 @Log 注解:
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
切面:
java
@Aspect
@Component
public class LogAspect {
@Pointcut("@annotation(com.example.annotation.Log)")
public void logPointcut() {}
@Around("logPointcut()")
public Object handler(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
Log log = method.getAnnotation(Log.class);
System.out.println("开始记录日志:" + log.value());
return pjp.proceed();
}
}
十一、实战案例:接口日志、监控、异常统一处理
如下需求在企业中非常常见:
- 记录接口入参
- 记录出参
- 记录执行耗时
- 记录异常
环绕通知即可实现:
java
@Around("@annotation(com.example.annotation.LogApi)")
public Object logApi(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object[] params = pjp.getArgs();
System.out.println("入参:" + Arrays.toString(params));
Object result;
try {
result = pjp.proceed();
} catch (Throwable e) {
System.out.println("异常:" + e.getMessage());
throw e;
}
System.out.println("出参:" + result);
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
return result;
}
十二、AOP 的常见问题与陷阱(非常重要)
问题 1:为什么 AOP 不能增强同类内部方法调用?
因为 Spring AOP 是基于 proxy 的,当对象内部调用自己的方法时,是"this.方法",不经过代理。
解决方案:
- 使用 AopContext.currentProxy()
- 或使用 AspectJ compile-time weaving(CTW)
问题 2:私有方法能被 AOP 拦截吗?
不能,因为动态代理无法代理 private 方法。
问题 3:为什么 @Transactional 对方法级别有效?
因为事务是通过 "环绕通知" + "代理" 实现的。
问题 4:构造方法能增强吗?
Spring AOP 不支持。
AspectJ 可以增强构造器与字段。
十三、AOP 在企业开发中的典型场景
| 场景 | 描述 |
|---|---|
| 日志记录 | 记录方法名、参数、返回值、耗时 |
| 事务控制 | @Transactional |
| 缓存处理 | @Cacheable、@CacheEvict |
| 接口限流 | 结合 Redis 实现 |
| 权限校验 | @PreAuthorize |
| 数据脱敏 | 返回前替换手机号等敏感字段 |
| API 访问统计 | 统计接口调用次数、耗时 |
| 请求幂等性 | 避免重复提交 |
十四、与 Spring MVC 配合使用
在 Controller 层 logging 示例:
java
@Around("execution(* com.example.controller.*.*(..))")
public Object controllerLog(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
String url = request.getRequestURI();
String ip = request.getRemoteAddr();
System.out.println("访问 URL: " + url);
System.out.println("来源 IP: " + ip);
return pjp.proceed();
}
十五、Spring AOP 设计理念总结(非常关键)
Spring AOP 的设计基于:
- 遵循 Spring 轻量级、无侵入、可扩展理念
- 不使用编译时 weaving,而是运行时代理
- 与 IOC 深度集成
- 利用动态代理完成增强,不改变业务代码
- 事务、缓存等核心模块全部基于 AOP
十六、AOP 学习路线建议
建议学习顺序:
- 理解代理模式(JDK / CGLIB)
- 掌握 AOP 的核心概念(JoinPoint / Advice / Pointcut)
- 学习 execution 表达式
- 学会自定义注解 + AOP
- 通过真实业务需求(日志、监控、权限)练习
- 源码级学习 AOP 实现原理
十七、完整总结(面试可背)
- Spring AOP 是一种基于代理的运行时增强技术
- 支持方法级别的横切逻辑注入
- 动态代理实现:JDK / CGLIB
- 切点表达式基于 execution
- 通知类型包括 Before、After、Around 等
- @Around 最强,可控制方法执行
- @Transactional、缓存、日志等都是 AOP 实现
- 内部方法调用无法被 AOP 增强
- 私有方法无法增强
- Spring AOP 不修改字节码,而 AspectJ 可以