题目
Spring AOP 中常用通知注解的作用及简单实现
信息
- 类型:问答
- 难度:⭐
考点
AOP基本概念,通知类型,切点表达式
快速回答
Spring AOP 提供了五种常用通知注解:
@Before:目标方法执行前执行@After:目标方法执行后执行(无论是否异常)@AfterReturning:目标方法成功返回后执行@AfterThrowing:目标方法抛出异常后执行@Around:包裹目标方法执行(可控制是否执行目标方法)
示例:使用 @Around 记录方法执行时间:
@Around("execution(* com.example.service.*.*(..))")
public Object logTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // 执行目标方法
long time = System.currentTimeMillis() - start;
System.out.println("方法执行耗时:" + time + "ms");
return result;
}
## 解析
一、原理说明
Spring AOP(面向切面编程)通过代理模式在运行时动态织入横切逻辑(如日志、事务等)。核心概念:
- 切面(Aspect):封装横切关注点的模块(使用
@Aspect注解的类) - 通知(Advice):切面中具体的执行逻辑(五种注解对应不同执行时机)
- 切点(Pointcut):定义通知应用的连接点(通过表达式指定)
二、通知类型详解与代码示例
1. 基础通知类型
@Aspect
@Component
public class LoggingAspect {
// 1. @Before 示例
@Before("execution(* com.example.service.UserService.*(..))")
public void beforeAdvice(JoinPoint jp) {
System.out.println("准备执行:" + jp.getSignature().getName());
}
// 2. @After 示例
@After("serviceMethods()")
public void afterAdvice(JoinPoint jp) {
System.out.println("方法执行结束:" + jp.getSignature().getName());
}
// 3. @AfterReturning 示例
@AfterReturning(
pointcut = "serviceMethods()",
returning = "result"
)
public void afterReturning(Object result) {
System.out.println("方法返回:" + result);
}
// 4. @AfterThrowing 示例
@AfterThrowing(
pointcut = "serviceMethods()",
throwing = "ex"
)
public void afterThrowing(Exception ex) {
System.out.println("方法异常:" + ex.getMessage());
}
// 切点复用定义
@Pointcut("execution(* com.example.service.*.*(..))")
private void serviceMethods() {}
}2. @Around 高级示例(方法耗时监控)
@Around("serviceMethods()")
public Object monitorTime(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
try {
// 执行目标方法(可在此处决定是否调用)
Object result = pjp.proceed();
return result;
} finally {
long duration = System.currentTimeMillis() - startTime;
String methodName = pjp.getSignature().toShortString();
System.out.println(methodName + " 执行耗时: " + duration + "ms");
}
}三、最佳实践
- 切点表达式优化:使用
@Pointcut定义可重用的切点表达式 - 性能考虑:避免在通知中执行耗时操作,尤其在高频方法上
- 异常处理:
@Around中调用pjp.proceed()需处理异常或重新抛出 - 最小作用域:精确限定切点范围(如
execution(* com.example.service.*.*(..))优于宽泛表达式)
四、常见错误
- 错误1:忘记在配置类添加
@EnableAspectJAutoProxy - 错误2:切点表达式错误导致通知未触发(如包路径拼写错误)
- 错误3:在
@Around中未调用proceed()导致目标方法未执行 - 错误4:对非 Spring 管理的对象(如 private 方法)使用 AOP
五、扩展知识
- 代理机制:Spring AOP 默认使用 JDK 动态代理(基于接口)或 CGLIB(基于类)
- 执行顺序:多个切面作用于同一方法时,可通过
@Order注解控制顺序 - 限制:Spring AOP 仅支持方法级别的拦截,不支持字段拦截
- 进阶:了解 AspectJ 可实现更强大的切点表达式(如
@within,@annotation)