一、什么是AOP?
AOP(Aspect-Oriented Programming) 是一种编程范式,用于解耦横切关注点(如日志、事务、权限)。通过将通用功能从业务逻辑中剥离,提升代码可维护性和复用性。
核心概念:
术语 | 说明 | 示例 |
---|---|---|
Aspect | 横切关注点的模块化 | @Aspect 注解的类 |
Join Point | 程序执行中的特定点(如方法调用) | UserService.addUser() |
Advice | 在连接点执行的动作 | @Before , @After |
Pointcut | 匹配连接点的表达式 | execution(* com.service.*.*(..)) |
Target | 被代理的对象 | 普通的Bean实例 |
二、Spring AOP 实现原理
动态代理机制:
-
JDK动态代理(默认)
-
要求目标类实现接口
-
运行时生成接口的代理类
Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { ... } );
-
-
CGLIB代理(无接口时)
-
通过继承目标类生成子类代理
-
需添加
spring-core
依赖
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
-
代理选择优先级:有接口 → JDK代理;无接口 → CGLIB(可通过
@EnableAspectJAutoProxy(proxyTargetClass=true)
强制使用CGLIB)
三、Spring AOP 实战步骤
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 定义切面
@Aspect
@Component
public class LoggingAspect {
// 定义切入点:匹配service包下所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// Before通知:方法执行前
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("【前置通知】调用方法: " + methodName);
}
// Around通知:包裹目标方法
@Around("serviceLayer()")
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;
}
}
3. 常用通知类型
注解 | 执行时机 | 是否可修改返回值 |
---|---|---|
@Before | 方法执行前 | ❌ |
@After | 方法执行后(无论是否异常) | ❌ |
@AfterReturning | 方法成功返回后 | ❌ |
@AfterThrowing | 方法抛出异常时 | ❌ |
@Around | 包裹目标方法 | ✅ |
四、切点表达式详解
常用表达式语法:
// 匹配包路径
execution(* com.service.*.*(..))
// 匹配注解
@annotation(com.annotation.RequireAuth)
// 匹配类
within(com.service.UserService+)
// 组合表达式
@Before("serviceLayer() && args(userId, ..)")
public void logWithParam(Long userId) { ... }
表达式组合运算符:
-
&&
(与) -
||
(或) -
!
(非)
五、最佳实践与避坑指南
-
代理失效场景
-
同类内部方法调用(通过
this
调用而非代理对象)
解决方案:
@Autowired private ApplicationContext context; public void internalCall() { // 错误:this.method() context.getBean(this.getClass()).method(); // 从容器获取代理对象 }
-
-
执行顺序控制
-
使用
@Order
注解指定切面顺序(值越小优先级越高)
@Aspect @Order(1) // 最先执行 public class SecurityAspect { ... }
-
-
性能优化
-
避免在频繁调用的方法上使用复杂切点
-
优先选择
execution
而非annotation
(前者在编译期优化)
-
六、实际应用场景
-
统一日志记录
-
声明式事务管理(
@Transactional
底层基于AOP) -
权限校验(结合自定义注解)
@Retention(RetentionPolicy.RUNTIME) public @interface AdminOnly {} @Aspect public class AuthAspect { @Before("@annotation(AdminOnly)") public void checkAdmin() { ... } }
-
接口限流与熔断
-
自动缓存处理
七、总结
-
优势:解耦横切逻辑、提升代码复用、降低维护成本
-
局限:仅作用于Spring容器中的Bean、无法拦截私有方法
-
适用场景:需要统一处理的非功能性需求
Spring AOP vs AspectJ:
特性 Spring AOP AspectJ 织入方式 运行时 编译期/类加载期 性能 中等 高 功能完整性 基础 全面(如字段拦截)