一、AOP核心概念矩阵
1.1 AOP术语对照表
术语 | 定义 | 典型实现 |
---|---|---|
Aspect | 横切关注点的模块化 | @Aspect 注解类 |
Join Point | 程序执行中的特定点 | 方法执行、异常处理 |
Advice | 在连接点执行的动作 | @Before , @Around 等 |
Pointcut | 匹配连接点的表达式 | execution(* com.service.*.*(..)) |
Weaving | 将切面应用到目标对象的过程 | 运行时动态代理 |
Target Object | 被代理的原始对象 | 被切入的Service类实例 |
二、代理机制深度解析
2.1 JDK动态代理 vs CGLIB
特性 | JDK动态代理 | CGLIB |
---|---|---|
代理方式 | 接口代理 | 子类继承 |
性能 | 调用快,创建慢 | 创建快,调用稍慢 |
限制 | 必须实现接口 | 不能代理final方法/类 |
内存占用 | 低 | 较高(生成子类) |
Spring默认策略 | 接口存在时优先使用 | 无接口时自动切换 |
配置调优:
# 强制使用CGLIB
spring.aop.proxy-target-class=true
2.2 代理创建流程图
三、切面编程实战模式
3.1 日志追踪切面
@Aspect
@Component
public class MethodLogger {
private static final Logger log = LoggerFactory.getLogger(MethodLogger.class);
@Around("execution(* com.example.service.*.*(..))")
public Object logMethod(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
String methodName = pjp.getSignature().getName();
try {
log.info("Entering {} with args: {}", methodName, pjp.getArgs());
Object result = pjp.proceed();
log.info("Exiting {} with result: {} (took {}ms)",
methodName, result, System.currentTimeMillis()-start);
return result;
} catch (Exception e) {
log.error("Error in {}: {}", methodName, e.getMessage());
throw e;
}
}
}
3.2 重试机制切面
@Aspect
@Component
public class MethodLogger {
private static final Logger log = LoggerFactory.getLogger(MethodLogger.class);
@Around("execution(* com.example.service.*.*(..))")
public Object logMethod(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
String methodName = pjp.getSignature().getName();
try {
log.info("Entering {} with args: {}", methodName, pjp.getArgs());
Object result = pjp.proceed();
log.info("Exiting {} with result: {} (took {}ms)",
methodName, result, System.currentTimeMillis()-start);
return result;
} catch (Exception e) {
log.error("Error in {}: {}", methodName, e.getMessage());
throw e;
}
}
}
四、性能优化策略
4.1 切入点表达式优化
// 低效写法(扫描范围过大)
@Before("execution(* com.example..*.*(..))")
// 优化方案(精确限定包路径)
@Before("execution(* com.example.service.*.*(..)) && within(@org.springframework.stereotype.Service *)")
4.2 切面执行耗时对比
切面类型 | 无AOP基准 | 简单前置通知 | 复杂环绕通知 |
---|---|---|---|
吞吐量(ops/s) | 15,000 | 14,200 | 12,500 |
平均延迟(ms) | 0.65 | 0.72 | 0.89 |
优化建议:
- 避免在切面中执行I/O操作
- 使用
@Around
时代理方法尽早执行proceed()
- 缓存频繁使用的连接点元数据
五、高级应用场景
5.1 动态数据源路由
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(com.example.annotation.ReadOnly)")
public void setReadOnlyDataSource(JoinPoint jp) {
DataSourceContextHolder.set(DataSourceType.READ_ONLY);
}
@After("@annotation(com.example.annotation.ReadOnly)")
public void clearDataSource() {
DataSourceContextHolder.clear();
}
}
5.2 接口限流控制
@Aspect
@Component
public class RateLimitAspect {
private final RateLimiter limiter = RateLimiter.create(100); // 100次/秒
@Around("execution(* com.example.api.*Controller.*(..))")
public Object rateLimit(ProceedingJoinPoint pjp) throws Throwable {
if(limiter.tryAcquire()) {
return pjp.proceed();
} else {
throw new RateLimitExceededException();
}
}
}
六、调试与问题排查
6.1 切面生效检查
// 查看代理类型
if(AopUtils.isJdkDynamicProxy(bean)) {
// JDK代理
} else if(AopUtils.isCglibProxy(bean)) {
// CGLIB代理
}
// 检查切面是否应用
Advised advised = (Advised) bean;
Advisor[] advisors = advised.getAdvisors();
6.2 常见问题解决方案
问题1:切面不生效
- 检查组件扫描路径是否包含切面类
- 确认目标方法是否为public
- 验证切入点表达式是否正确
问题2:循环依赖
// 使用@Lazy延迟注入
@Autowired
public MyService(@Lazy MyAspect aspect) {
this.aspect = aspect;
}
七、现代Spring Boot最佳实践
7.1 自动代理配置
# 开启AspectJ自动代理
spring.aop.auto=true
spring.aop.proxy-target-class=true
# 排除特定包的切面扫描
spring.aop.exclude=com.example.debug.*
7.2 监控切面性能
@Aspect
@Component
public class MetricsAspect {
@Autowired
private MeterRegistry registry;
@Around("@within(org.springframework.web.bind.annotation.RestController)")
public Object measureApi(ProceedingJoinPoint pjp) throws Throwable