Spring Boot AOP 最佳实践指南
Spring AOP(面向切面编程,Aspect-Oriented Programming)是 Spring 框架的重要组成部分,用于将日志、事务、监控等横切关注点从业务逻辑中抽离出来。Spring Boot 进一步简化了 AOP 的集成和配置。
1. 添加依赖
在 Spring Boot 项目中,只需添加 AOP 依赖即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
如果需要使用注解驱动的事务(@Transactional),还会用到:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
2. AOP 核心概念回顾
- Aspect(切面):封装横切逻辑的模块
- Join Point(连接点):程序运行过程中的点(如方法调用)
- Advice(通知):在连接点执行的操作(前置、后置、异常、环绕)
- Pointcut(切点):匹配连接点的规则
- Weaving(织入):将切面应用到目标对象的过程
3. Spring AOP 的代理机制
Spring AOP 基于代理实现(默认是运行时代理):
- JDK 动态代理:如果目标类实现了接口,则默认使用 JDK 动态代理(只能代理接口方法)
- CGLIB 代理:如果目标类没有实现接口,Spring 使用 CGLIB 生成子类代理
注意:
- private 和 final 方法无法被代理
- 自调用(同类方法内部调用)不会触发 AOP
4. 启用 AOP 配置
Spring Boot 默认会开启 AOP,但有时我们需要更精细的控制:
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
public class AopConfig {
}
- exposeProxy = true:允许通过 AopContext.currentProxy() 获取当前代理对象,解决自调用问题
- proxyTargetClass = true:强制使用 CGLIB 代理(即使有接口)
5. 定义切面与通知
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("调用方法: " + joinPoint.getSignature().getName() +
" 参数: " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("方法返回值: " + result);
}
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void logAfterThrowing(Exception ex) {
System.out.println("方法抛出异常: " + ex.getMessage());
}
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed();
long cost = System.currentTimeMillis() - start;
System.out.println(pjp.getSignature() + " 执行耗时: " + cost + "ms");
return result;
} catch (Throwable ex) {
System.out.println("方法执行异常: " + ex.getMessage());
throw ex;
}
}
}
6. 使用自定义注解增强
创建一个注解,用来标记需要监控的方法:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
切面:
@Aspect
@Component
public class LogExecutionTimeAspect {
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long cost = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " 执行耗时: " + cost + "ms");
return proceed;
}
}
使用:
@Service
public class UserService {
@LogExecutionTime
public void longRunningTask() {
// 模拟耗时任务
Thread.sleep(1000);
}
}
7. 实战场景
7.1 统一日志打印
可在切面中统一收集 请求参数、返回值、耗时,避免在业务代码里写重复的日志。
7.2 性能监控
结合 Prometheus、Micrometer 等工具,将方法耗时指标上报到监控系统。
7.3 权限校验
通过 AOP 拦截 @RequiresPermission 注解的方法,在执行前做权限验证。
7.4 异常处理
在 Service 层拦截异常,统一包装成业务异常对象返回。
8. 性能与最佳实践
- 保持切面简洁:切面只处理横切关注点,不要写复杂业务逻辑
- 避免过度使用:AOP 不适合所有场景,能用 Spring MVC/Security/拦截器解决的尽量用框架本身
- 高性能场景下注意开销:日志/监控类切面可异步化(如写入消息队列)
- 切面顺序:多个切面时可用 @Order 指定优先级
- 调试技巧:确认切点表达式正确,可以用 JoinPoint 打印方法信息
9. 常见问题
- AOP 不生效的原因
- Bean 未被 Spring 管理(缺少 @Component)
- 方法是 private / final
- 自调用未经过代理
- 切点表达式错误
- 解决自调用问题
- 使用 AopContext.currentProxy() 获取代理调用
- 或者拆分逻辑到其他 Bean
总结:
Spring Boot 对 AOP 的支持非常完善,只需引入依赖、编写切面类即可。合理使用 AOP 可以极大提升 日志监控、性能统计、权限控制 等横切关注点的复用性和可维护性。