AOP
AOP(Aspect-Oriented Programming,面向切面编程)是对 OOP 的补充,用于将横切关注点(日志、事务、安全等)从业务逻辑中剥离,统一管理。
核心概念
| 概念 | 说明 |
|---|---|
| Aspect(切面) | 横切关注点的模块化,包含 Pointcut + Advice |
| Join Point(连接点) | 程序执行的某个点,Spring AOP 中特指方法执行 |
| Pointcut(切点) | 匹配连接点的表达式,决定切面在哪里织入 |
| Advice(通知) | 切面在连接点执行的动作(前置、后置、环绕等) |
| Target(目标对象) | 被切面增强的对象 |
| Proxy(代理) | Spring AOP 为目标对象创建的代理(JDK 动态代理或 CGLIB) |
| Weaving(织入) | 将切面应用到目标对象并创建代理的过程 |
Spring AOP vs AspectJ
| 对比 | Spring AOP | AspectJ |
|---|---|---|
| 织入时机 | 运行时(代理) | 编译时 / 加载时 |
| 支持范围 | 仅 Spring Bean 的方法执行 | 任意 Java 代码 |
| 性能 | 略低(代理开销) | 更高 |
| 使用复杂度 | 简单 | 复杂 |
| 适用场景 | 常规业务场景 | 高性能或非 Spring 环境 |
Spring AOP 默认使用 AspectJ 的注解语法,但底层织入是代理方式。
快速使用
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>五种 Advice 类型
@Aspect
@Component
public class LogAspect {
// 切点表达式复用
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 方法执行前
@Before("serviceLayer()")
public void before(JoinPoint jp) {
System.out.println("Before: " + jp.getSignature().getName());
}
// 方法正常返回后
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void afterReturning(JoinPoint jp, Object result) {
System.out.println("Return: " + result);
}
// 方法抛出异常后
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void afterThrowing(JoinPoint jp, Exception ex) {
System.out.println("Exception: " + ex.getMessage());
}
// 无论正常或异常都执行(不能获取返回值)
@After("serviceLayer()")
public void after(JoinPoint jp) {
System.out.println("After: " + jp.getSignature().getName());
}
// 环绕通知,功能最强,可控制是否执行目标方法
@Around("serviceLayer()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // 执行目标方法
long cost = System.currentTimeMillis() - start;
System.out.println("耗时: " + cost + "ms");
return result;
}
}执行顺序(正常):@Around前 → @Before → 目标方法 → @AfterReturning → @After → @Around后
执行顺序(异常):@Around前 → @Before → 目标方法抛异常 → @AfterThrowing → @After
Pointcut 表达式
execution 表达式
execution(修饰符? 返回类型 类路径? 方法名(参数) 异常?)
| 示例 | 说明 |
|---|---|
execution(* com.example.service.*.*(..)) | service 包下所有类的所有方法 |
execution(* com.example..*.*(..)) | example 包及子包下所有方法 |
execution(public * *(..)) | 所有 public 方法 |
execution(* *Service.*(..)) | 类名以 Service 结尾的所有方法 |
execution(* com.example.UserService.get*(..)) | UserService 中 get 开头的方法 |
其他切点指示符
// 注解匹配:标注了 @Log 的方法
@Pointcut("@annotation(com.example.annotation.Log)")
public void logAnnotation() {}
// Bean 名称匹配
@Pointcut("bean(userService)")
public void userServiceBean() {}
// 参数类型匹配
@Pointcut("args(java.lang.String)")
public void stringArgs() {}
// 组合使用
@Pointcut("serviceLayer() && logAnnotation()")
public void serviceWithLog() {}获取注解参数
@Around("@annotation(log)")
public Object aroundLog(ProceedingJoinPoint pjp, Log log) throws Throwable {
System.out.println("操作描述: " + log.value());
return pjp.proceed();
}常见使用场景
操作日志
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OperLog {
String value() default "";
}
@Aspect
@Component
public class OperLogAspect {
@Around("@annotation(operLog)")
public Object record(ProceedingJoinPoint pjp, OperLog operLog) throws Throwable {
String method = pjp.getSignature().toShortString();
Object[] args = pjp.getArgs();
Object result = pjp.proceed();
// 持久化日志...
return result;
}
}接口限流
@Around("@annotation(rateLimit)")
public Object rateLimit(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
String key = pjp.getSignature().toString();
if (!rateLimiter.tryAcquire(key, rateLimit.qps())) {
throw new RuntimeException("请求过于频繁");
}
return pjp.proceed();
}参数校验
@Before("execution(* com.example.controller.*.*(..))")
public void validateArgs(JoinPoint jp) {
for (Object arg : jp.getArgs()) {
if (arg == null) throw new IllegalArgumentException("参数不能为空");
}
}代理机制
目标类实现了接口 → JDK 动态代理(基于接口)
目标类未实现接口 → CGLIB 代理(基于子类继承)
强制使用 CGLIB(proxyTargetClass = true):
@EnableAspectJAutoProxy(proxyTargetClass = true)或在配置文件中:
spring:
aop:
proxy-target-class: true注意事项
- 自调用失效:同类内部方法互调不经过代理,AOP 不生效。解决方案:注入自身 Bean 或用
AopContext.currentProxy() - private 方法:Spring AOP 基于代理,无法拦截 private 方法
- final 类/方法:CGLIB 无法代理 final 类或方法
- 多切面排序:用
@Order(n)控制多个切面的执行顺序,n 越小优先级越高