AOP (面向切面编程) 深度详解
一、AOP核心概念再解析
1. 切面(Aspect)
切面是模块化横切关注点的单元,类似于OOP中的类。一个切面包含:
- 多个通知(Advice)
- 切入点(Pointcut)定义
- 引入(Introduction)声明
@Aspect
@Component
public class LoggingAspect {
// 切入点定义
@Pointcut("execution(* com.example.service.*.*(..))")
private void serviceLayer() {}
// 通知定义
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("调用方法: " + joinPoint.getSignature().getName());
}
}
2. 连接点(Join Point)
程序执行过程中的特定点,如:
- 方法调用
- 方法执行
- 构造器调用
- 字段访问
- 异常处理
Spring AOP仅支持方法执行连接点
3. 通知(Advice)类型详解
(1) 前置通知(@Before)
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
// 可以访问方法参数
Object[] args = joinPoint.getArgs();
// 可以获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 可以获取目标对象
Object target = joinPoint.getTarget();
}
(2) 后置返回通知(@AfterReturning)
@AfterReturning(
pointcut = "execution(* com.example.service.*.*(..))",
returning = "result"
)
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
// 可以处理方法返回值
if(result != null) {
System.out.println("方法返回: " + result.toString());
}
}
(3) 异常通知(@AfterThrowing)
@AfterThrowing(
pointcut = "execution(* com.example.service.*.*(..))",
throwing = "ex"
)
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
// 可以处理特定异常
if(ex instanceof NullPointerException) {
System.out.println("空指针异常: " + ex.getMessage());
}
}
(4) 最终通知(@After)
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice(JoinPoint joinPoint) {
// 无论方法是否抛出异常都会执行
System.out.println("方法执行结束");
}
(5) 环绕通知(@Around)
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法执行前
long startTime = System.currentTimeMillis();
try {
// 执行目标方法
Object result = joinPoint.proceed();
// 方法执行后
long endTime = System.currentTimeMillis();
System.out.println("方法执行耗时: " + (endTime - startTime) + "ms");
return result;
} catch (Exception ex) {
// 异常处理
System.err.println("方法执行异常: " + ex.getMessage());
throw ex;
}
}
二、切入点表达式详解
1. 基本语法
execution([修饰符] 返回类型 [类名].方法名(参数) [异常])
2. 通配符使用
*
匹配任意字符(除包分隔符)..
匹配任意多个字符(包括包分隔符)或多级目录/多个参数
3. 常见表达式示例
// 匹配所有public方法
execution(public * *(..))
// 匹配所有set开头的方法
execution(* set*(..))
// 匹配com.example.service包下所有类的所有方法
execution(* com.example.service.*.*(..))
// 匹配com.example.service包及其子包下所有类的所有方法
execution(* com.example.service..*.*(..))
// 匹配UserService接口的所有实现类的方法
execution(* com.example.service.UserService+.*(..))
// 匹配所有只有一个String参数的方法
execution(* *(String))
// 匹配所有第一个参数为String的方法
execution(* *(String,..))
4. 组合切入点
可以使用逻辑运算符组合多个切入点:
&&
(and)||
(or)!
(not)
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.UserService.login(..))")
public void serviceMethodsExceptLogin() {}
三、AOP实现原理深度剖析
1. Spring AOP代理机制
(1) JDK动态代理
- 条件:目标类实现了接口
- 原理:基于Java反射API创建接口的代理实例
- 特点:
- 只能代理接口方法
- 性能较好
- 无需额外依赖
(2) CGLIB代理
- 条件:目标类没有实现接口
- 原理:通过生成目标类的子类来实现代理
- 特点:
- 可以代理类方法(final方法除外)
- 需要CGLIB库
- 创建代理对象速度较慢但运行效率高
2. 代理创建过程
- 检查目标对象是否实现了接口
- 如果有接口,使用JDK动态代理
- 如果没有接口,使用CGLIB
- 可以通过配置强制使用CGLIB:
@EnableAspectJAutoProxy(proxyTargetClass = true)
3. AOP执行流程
- 容器启动时解析@Aspect注解的类
- 根据切入点表达式匹配目标Bean
- 为匹配的Bean创建代理对象
- 当调用代理对象方法时:
- 执行前置通知
- 调用目标方法
- 执行后置通知
- 如有异常执行异常通知
- 最终执行最终通知
四、高级AOP特性
1. 引入(Introduction)
为现有类添加新方法和属性
public interface Auditable {
void setAuditUser(String user);
String getAuditUser();
}
@Aspect
@Component
public class AuditableIntroductionAspect {
@DeclareParents(value = "com.example.service.*", defaultImpl = AuditableImpl.class)
public static Auditable auditable;
}
public class AuditableImpl implements Auditable {
private String auditUser;
public void setAuditUser(String user) {
this.auditUser = user;
}
public String getAuditUser() {
return auditUser;
}
}
2. 基于注解的切入点
// 定义自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
String value() default "";
}
// 使用注解定义切入点
@Pointcut("@annotation(com.example.Loggable)")
public void loggableMethod() {}
// 应用通知
@Before("loggableMethod()")
public void logMethod(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Loggable loggable = method.getAnnotation(Loggable.class);
System.out.println("执行标记为Loggable的方法: " + method.getName()
+ ", 描述: " + loggable.value());
}
3. 获取方法参数
@Before("execution(* com.example.service.*.*(..)) && args(username,..)")
public void validateUser(String username) {
if("admin".equals(username)) {
System.out.println("管理员操作");
}
}
4. 切面排序
使用@Order注解或实现Ordered接口
@Aspect
@Order(1)
@Component
public class LoggingAspect {
// 这个切面会先执行
}
@Aspect
@Order(2)
@Component
public class ValidationAspect {
// 这个切面会后执行
}
五、性能优化与最佳实践
1. 性能考虑
- 尽量减少切入点表达式的复杂度
- 避免在通知中执行耗时操作
- 对于频繁调用的方法,考虑环绕通知的缓存
- 合理使用@Order调整切面顺序
2. 最佳实践
- 日志记录:统一记录方法入参、返回值和异常
- 性能监控:统计方法执行时间
- 重试机制:对特定异常进行自动重试
- 缓存:方法级别缓存
- 事务管理:声明式事务
- 安全控制:权限检查
3. 常见陷阱
- 自调用问题(同一个类中方法互相调用不会触发AOP)
- final方法不能被CGLIB代理
- private方法不能被代理
- 切面过多可能导致性能下降
- 异常处理不当可能掩盖原始异常
六、Spring AOP与AspectJ对比
特性 | Spring AOP | AspectJ |
---|---|---|
实现方式 | 动态代理 | 字节码增强(编译时/加载时) |
连接点支持 | 仅方法执行 | 方法、构造器、字段、静态初始化块等 |
性能 | 运行时开销较大 | 编译时织入无运行时开销 |
复杂度 | 简单 | 复杂 |
依赖 | 仅需Spring核心 | 需要AspectJ编译器或织入器 |
适用场景 | 简单切面需求 | 复杂切面需求 |
配置方式 | XML或注解 | 注解或AspectJ特定语法 |
七、实际应用案例
1. 方法级缓存实现
@Aspect
@Component
public class CacheAspect {
private final CacheManager cacheManager;
public CacheAspect(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Around("@annotation(cacheable)")
public Object cacheResult(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
String cacheName = cacheable.value();
String key = generateCacheKey(joinPoint);
Cache cache = cacheManager.getCache(cacheName);
Cache.ValueWrapper cachedValue = cache.get(key);
if(cachedValue != null) {
return cachedValue.get();
}
Object result = joinPoint.proceed();
cache.put(key, result);
return result;
}
private String generateCacheKey(ProceedingJoinPoint joinPoint) {
// 生成基于方法签名和参数的缓存键
return joinPoint.getSignature().toLongString() +
Arrays.toString(joinPoint.getArgs());
}
}
// 使用注解
@Cacheable("userCache")
public User getUserById(Long id) {
// 数据库查询
}
2. 接口限流
@Aspect
@Component
public class RateLimitAspect {
private final RateLimiter rateLimiter = RateLimiter.create(100); // 每秒100个请求
@Around("execution(* com.example.api..*(..))")
public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
if(rateLimiter.tryAcquire()) {
return joinPoint.proceed();
} else {
throw new RateLimitExceededException("请求过于频繁,请稍后再试");
}
}
}
3. 操作日志记录
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperationLogService logService;
@AfterReturning(
pointcut = "@annotation(operationLog)",
returning = "result"
)
public void logOperation(JoinPoint joinPoint, OperationLog operationLog, Object result) {
String operation = operationLog.value();
String operator = SecurityContext.getCurrentUser();
String params = Arrays.toString(joinPoint.getArgs());
OperationLogEntity log = new OperationLogEntity();
log.setOperation(operation);
log.setOperator(operator);
log.setParams(params);
log.setResult(result != null ? result.toString() : null);
log.setOperationTime(new Date());
logService.save(log);
}
}
// 使用注解
@OperationLog("创建用户")
public User createUser(User user) {
// 创建用户逻辑
}
通过以上深度解析,我们可以看到AOP的强大之处在于它能够将横切关注点模块化,使业务逻辑更加清晰,同时提高了代码的可维护性和可重用性。合理使用AOP可以显著提升应用程序的质量和开发效率。