AOP切面
2025/11/12...大约 4 分钟
AOP
1. 定义切面类
切面类需使用 @Aspect 标识,并纳入 Spring 容器管理:
@Aspect
@Component
@Slf4j
public class CacheAspect {
}2. 定义切点(Pointcut)
切点用于描述 哪些方法/类/连接点 需要被拦截。
2.1 常见 Pointcut 表达式
2.1.1 @annotation
匹配 方法上 标记了指定注解的连接点:
// 匹配所有带 @CacheAble 注解的方法
@Pointcut("@annotation(org.example.aop.CacheAble)")
public void cacheAbleCut(){}技巧:
可在 Advice 入参中直接绑定注解对象:@Around("cacheAbleCut() && @annotation(cacheAble)") public Object around(ProceedingJoinPoint jp, CacheAble cacheAble) { ... }这样可以直接获取注解属性值,无需手动反射。
2.1.2 execution
根据 方法签名 匹配:
// 匹配所有公共方法
@Pointcut("execution(public * *(..))")
public void anyPublicMethod() {}
// 匹配所有以 set 开头的方法
@Pointcut("execution(* set*(..))")
public void anySetMethod() {}
// 匹配 com.example.service 包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 匹配 UserDao 接口的所有实现方法
@Pointcut("execution(* com.example.dao.UserDao.*(..))")
public void userDaoMethods() {}
// 匹配返回值类型为 User 的方法
@Pointcut("execution(com.example.model.User *(..))")
public void methodsReturningUser() {}2.1.3 @within 与 @target
匹配 类级别 注解:
// 匹配带 @Service 注解类中的所有方法
@Pointcut("@within(org.springframework.stereotype.Service)")
public void serviceAnnotationMethods() {}
// 匹配目标对象带 @Repository 注解的连接点
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void repositoryAnnotationMethods() {}区别:
@within→ 检查 声明类 是否有注解@target→ 检查 运行时目标类 是否有注解(Spring 代理场景可能不同)
2.1.4 组合切点
支持 &&(与)、||(或)、!(非):
// 在 service 包中且为公共方法
@Pointcut("serviceMethods() && anyPublicMethod()")
public void servicePublicMethods() {}
// service 包 或 dao 包方法
@Pointcut("serviceMethods() || daoMethods()")
public void serviceOrDaoMethods() {}
// 非事务性 service 方法
@Pointcut("serviceMethods() && !transactionalMethods()")
public void serviceNonTransactionalMethods() {}2.2 其他常用匹配方式
2.2.1 args
绑定方法参数:
@Pointcut("execution(* com.example..*(..)) && args(userId,..)")
public void methodWithUserIdArg(String userId) {}Advice 可直接接收参数:
@Before("methodWithUserIdArg(userId)")
public void logUser(String userId) { ... }2.2.2 this / target
this(proxy):绑定 代理对象target(obj):绑定 真实目标对象
@Around("this(proxy) && target(service)")
public Object around(ProceedingJoinPoint jp, Object proxy, MyService service) { ... }3.定义切面
在 AOP 中,切面逻辑主要通过以下几种 Advice 来实现:
| 通知类型 | 注解 | 作用时机 |
|---|---|---|
| 前置通知 | @Before | 目标方法执行前 |
| 后置通知 | @After | 目标方法执行后(无论是否异常) |
| 返回通知 | @AfterReturning | 目标方法成功返回后 |
| 异常通知 | @AfterThrowing | 目标方法抛出异常后 |
| 环绕通知 | @Around | 在目标方法前后都可执行,并可决定是否执行目标方法 |
其中ProceedingJoinPoint是JoinPoint的子类,由于Around需要执行源方法,ProceedingJoinPoint针对@Around环绕通知提供了proceed()来调用源方法。
3.1 前置通知 @Before
@Before("cacheAbleCut()")
public void beforeMethod(JoinPoint joinPoint) {
log.info("执行前置通知: 方法 = {}, 参数 = {}",
joinPoint.getSignature().getName(),
Arrays.toString(joinPoint.getArgs()));
}3.2 后置通知 @After
@After("cacheAbleCut()")
public void afterMethod(JoinPoint joinPoint) {
log.info("执行后置通知: 方法 = {}", joinPoint.getSignature().getName());
}3.3 返回通知 @AfterReturning
@AfterReturning(pointcut = "cacheAbleCut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
log.info("方法 {} 成功返回,返回值 = {}", joinPoint.getSignature().getName(), result);
}3.4 异常通知 @AfterThrowing
@AfterThrowing(pointcut = "cacheAbleCut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
log.error("方法 {} 抛出异常: {}", joinPoint.getSignature().getName(), ex.getMessage(), ex);
}3.5 环绕通知 @Around
@Around 是功能最强的通知,可以控制方法执行与否,并在执行前后做处理:
@Around("cacheAbleCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
log.info("环绕通知开始 - 方法: {}", pjp.getSignature().getName());
long startTime = System.currentTimeMillis();
// 执行目标方法
Object result = pjp.proceed();
long endTime = System.currentTimeMillis();
log.info("环绕通知结束 - 耗时: {}ms", (endTime - startTime));
// 返回的即最终方法执行后返回的结果,可以自定义随意返回
return result;
}高级用法:绑定注解实例
如果希望直接在环绕通知的方法参数中获取到目标方法上的注解对象,可以这样写:
@Around("aopPoint() && @annotation(rateLimiterAccessInterceptor)")
public Object doRouter(ProceedingJoinPoint jp,
RateLimiterAccessInterceptor rateLimiterAccessInterceptor) throws Throwable {
log.info("限流检查: key={}, permits={}",
rateLimiterAccessInterceptor.key(),
rateLimiterAccessInterceptor.permits());
return jp.proceed();
}说明:
&& @annotation(rateLimiterAccessInterceptor)
限定切点只匹配带有该注解的方法,并且将注解实例注入到参数中。- 方法参数
RateLimiterAccessInterceptor rateLimiterAccessInterceptor
直接就是方法上真实存在的注解对象,不需要手动反射getMethod()再getAnnotation()。
示例目标方法:
@RateLimiterAccessInterceptor(key = "userLogin", permits = 5)
public void login(String username) {
// 登录逻辑
}执行时 AOP 会自动把 @RateLimiterAccessInterceptor 注解实例注入到 doRouter() 的第二个参数中。