Skip to content

AOP 支持

LCGYL Framework 提供轻量级的 AOP(面向切面编程)支持,基于 JDK 动态代理实现方法拦截。

什么是 AOP?

AOP 允许你在不修改原有代码的情况下,为方法添加横切关注点(如日志、事务、权限检查等)。

核心概念

  • 切面(Aspect):横切关注点的模块化
  • 连接点(Join Point):程序执行的某个点(如方法调用)
  • 通知(Advice):在连接点执行的动作
  • 切入点(Pointcut):匹配连接点的表达式

定义拦截器

基本拦截器

java
@Component
public class LoggingInterceptor implements MethodInterceptor {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
    
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        String methodName = invocation.getMethod().getName();
        Object[] args = invocation.getArguments();
        
        // 方法执行前
        logger.info("调用方法: {},参数: {}", methodName, Arrays.toString(args));
        long startTime = System.currentTimeMillis();
        
        try {
            // 执行原方法
            Object result = invocation.proceed();
            
            // 方法执行后
            long duration = System.currentTimeMillis() - startTime;
            logger.info("方法 {} 执行完成,耗时: {}ms,返回: {}", methodName, duration, result);
            
            return result;
        } catch (Throwable e) {
            // 异常处理
            logger.error("方法 {} 执行异常: {}", methodName, e.getMessage());
            throw e;
        }
    }
}

使用注解定义

java
@Aspect
@Component
public class LoggingAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        logger.info("调用方法: {}", joinPoint.getSignature().getName());
    }
    
    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        logger.info("方法执行完成: {}", joinPoint.getSignature().getName());
    }
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long duration = System.currentTimeMillis() - start;
        logger.info("方法 {} 耗时: {}ms", joinPoint.getSignature().getName(), duration);
        return result;
    }
}

通知类型

@Before - 前置通知

方法执行前调用。

java
@Aspect
@Component
public class SecurityAspect {
    
    @Before("@annotation(RequiresPermission)")
    public void checkPermission(JoinPoint joinPoint) {
        // 检查权限
        User user = SecurityContext.getCurrentUser();
        if (user == null) {
            throw new UnauthorizedException("用户未登录");
        }
        
        RequiresPermission annotation = getAnnotation(joinPoint, RequiresPermission.class);
        if (!user.hasPermission(annotation.value())) {
            throw new ForbiddenException("权限不足");
        }
    }
}

@After - 后置通知

方法执行后调用(无论是否异常)。

java
@Aspect
@Component
public class ResourceAspect {
    
    @After("execution(* com.example.service.*.*(..))")
    public void releaseResource(JoinPoint joinPoint) {
        // 释放资源
        ResourceHolder.release();
    }
}

@AfterReturning - 返回通知

方法正常返回后调用。

java
@Aspect
@Component
public class AuditAspect {
    
    @AfterReturning(
        pointcut = "execution(* com.example.service.UserService.create*(..))",
        returning = "result"
    )
    public void auditCreate(JoinPoint joinPoint, Object result) {
        // 记录审计日志
        auditService.log("CREATE", result);
    }
}

@AfterThrowing - 异常通知

方法抛出异常后调用。

java
@Aspect
@Component
public class ExceptionAspect {
    
    @AfterThrowing(
        pointcut = "execution(* com.example.service.*.*(..))",
        throwing = "ex"
    )
    public void handleException(JoinPoint joinPoint, Throwable ex) {
        // 记录异常
        logger.error("方法 {} 抛出异常: {}", 
            joinPoint.getSignature().getName(), 
            ex.getMessage());
        
        // 发送告警
        alertService.sendAlert(ex);
    }
}

@Around - 环绕通知

完全控制方法执行。

java
@Aspect
@Component
public class TransactionAspect {
    
    @Around("@annotation(Transactional)")
    public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        Transaction tx = transactionManager.begin();
        
        try {
            Object result = joinPoint.proceed();
            tx.commit();
            return result;
        } catch (Throwable e) {
            tx.rollback();
            throw e;
        }
    }
}

切入点表达式

execution 表达式

匹配方法执行。

java
// 匹配所有 public 方法
@Pointcut("execution(public * *(..))")

// 匹配 UserService 的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")

// 匹配 service 包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")

// 匹配 service 包及子包下所有类的所有方法
@Pointcut("execution(* com.example.service..*.*(..))")

// 匹配返回 User 的方法
@Pointcut("execution(User com.example.service.*.*(..))")

// 匹配第一个参数为 Long 的方法
@Pointcut("execution(* com.example.service.*.*(Long, ..))")

@annotation 表达式

匹配带有指定注解的方法。

java
// 匹配带有 @Transactional 注解的方法
@Pointcut("@annotation(com.example.annotation.Transactional)")

// 匹配带有 @Cacheable 注解的方法
@Pointcut("@annotation(Cacheable)")

within 表达式

匹配指定类型内的方法。

java
// 匹配 UserService 内的所有方法
@Pointcut("within(com.example.service.UserService)")

// 匹配 service 包下所有类的方法
@Pointcut("within(com.example.service.*)")

组合表达式

java
// AND
@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(Transactional)")

// OR
@Pointcut("execution(* com.example.service.*.*(..)) || execution(* com.example.repository.*.*(..))")

// NOT
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.*.get*(..))")

自定义注解

定义注解

java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {
    String value() default "";
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
    int maxAttempts() default 3;
    long delay() default 1000;
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cached {
    String key() default "";
    long ttl() default 3600;
}

使用注解

java
@Component
public class UserService {
    
    @Timed("user.findById")
    public User findById(Long id) {
        return userRepository.findById(id);
    }
    
    @Retry(maxAttempts = 3, delay = 1000)
    public void sendEmail(User user) {
        emailService.send(user.getEmail(), "Welcome!");
    }
    
    @Cached(key = "user:list", ttl = 300)
    public List<User> findAll() {
        return userRepository.findAll();
    }
}

实现切面

java
@Aspect
@Component
public class TimedAspect {
    
    @Around("@annotation(timed)")
    public Object measureTime(ProceedingJoinPoint joinPoint, Timed timed) throws Throwable {
        String name = timed.value().isEmpty() 
            ? joinPoint.getSignature().getName() 
            : timed.value();
        
        long start = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            long duration = System.currentTimeMillis() - start;
            metricsService.recordTiming(name, duration);
        }
    }
}

@Aspect
@Component
public class RetryAspect {
    
    @Around("@annotation(retry)")
    public Object retry(ProceedingJoinPoint joinPoint, Retry retry) throws Throwable {
        int attempts = 0;
        Throwable lastException = null;
        
        while (attempts < retry.maxAttempts()) {
            try {
                return joinPoint.proceed();
            } catch (Throwable e) {
                lastException = e;
                attempts++;
                if (attempts < retry.maxAttempts()) {
                    Thread.sleep(retry.delay());
                }
            }
        }
        
        throw lastException;
    }
}

@Aspect
@Component
public class CachedAspect {
    
    @Inject
    private CacheService cacheService;
    
    @Around("@annotation(cached)")
    public Object cache(ProceedingJoinPoint joinPoint, Cached cached) throws Throwable {
        String key = cached.key().isEmpty() 
            ? generateKey(joinPoint) 
            : cached.key();
        
        // 尝试从缓存获取
        Object cachedValue = cacheService.get(key);
        if (cachedValue != null) {
            return cachedValue;
        }
        
        // 执行方法
        Object result = joinPoint.proceed();
        
        // 存入缓存
        cacheService.put(key, result, cached.ttl());
        
        return result;
    }
}

拦截器链

多个拦截器

java
@Component
@Order(1)
public class SecurityInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 安全检查
        checkSecurity();
        return invocation.proceed();
    }
}

@Component
@Order(2)
public class LoggingInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 日志记录
        log(invocation);
        return invocation.proceed();
    }
}

@Component
@Order(3)
public class TransactionInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 事务管理
        return executeInTransaction(invocation);
    }
}

执行顺序

请求 → SecurityInterceptor → LoggingInterceptor → TransactionInterceptor → 目标方法
响应 ← SecurityInterceptor ← LoggingInterceptor ← TransactionInterceptor ← 目标方法

代理工厂

创建代理

java
// 创建代理工厂
ProxyFactory factory = new ProxyFactory();
factory.setTarget(new UserServiceImpl());
factory.addInterceptor(new LoggingInterceptor());
factory.addInterceptor(new TransactionInterceptor());

// 获取代理对象
UserService proxy = factory.getProxy();

// 调用方法(会经过拦截器)
proxy.createUser(user);

JDK 动态代理

java
// 基于接口的代理
public interface UserService {
    User findById(Long id);
}

@Component
public class UserServiceImpl implements UserService {
    @Override
    public User findById(Long id) {
        return userRepository.findById(id);
    }
}

// 自动创建 JDK 动态代理

实际应用场景

1. 日志记录

java
@Aspect
@Component
public class MethodLoggingAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(MethodLoggingAspect.class);
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        
        logger.debug("[{}#{}] 开始执行,参数: {}", className, methodName, Arrays.toString(args));
        
        long start = System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
            long duration = System.currentTimeMillis() - start;
            logger.debug("[{}#{}] 执行完成,耗时: {}ms,返回: {}", className, methodName, duration, result);
            return result;
        } catch (Throwable e) {
            long duration = System.currentTimeMillis() - start;
            logger.error("[{}#{}] 执行异常,耗时: {}ms,异常: {}", className, methodName, duration, e.getMessage());
            throw e;
        }
    }
}

2. 性能监控

java
@Aspect
@Component
public class PerformanceAspect {
    
    @Inject
    private MetricsService metricsService;
    
    @Around("@annotation(Monitored)")
    public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
        String metricName = joinPoint.getSignature().toShortString();
        
        long start = System.nanoTime();
        try {
            Object result = joinPoint.proceed();
            metricsService.recordSuccess(metricName, System.nanoTime() - start);
            return result;
        } catch (Throwable e) {
            metricsService.recordFailure(metricName, System.nanoTime() - start);
            throw e;
        }
    }
}

3. 权限控制

java
@Aspect
@Component
public class AuthorizationAspect {
    
    @Before("@annotation(requiresRole)")
    public void checkRole(JoinPoint joinPoint, RequiresRole requiresRole) {
        User user = SecurityContext.getCurrentUser();
        
        if (user == null) {
            throw new UnauthorizedException("请先登录");
        }
        
        String[] requiredRoles = requiresRole.value();
        boolean hasRole = Arrays.stream(requiredRoles)
            .anyMatch(user::hasRole);
        
        if (!hasRole) {
            throw new ForbiddenException("权限不足,需要角色: " + Arrays.toString(requiredRoles));
        }
    }
}

4. 缓存管理

java
@Aspect
@Component
public class CacheAspect {
    
    @Inject
    private CacheManager cacheManager;
    
    @Around("@annotation(cacheable)")
    public Object handleCache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
        String cacheName = cacheable.value();
        String key = generateKey(joinPoint);
        
        Cache cache = cacheManager.getCache(cacheName);
        
        // 查询缓存
        Object cached = cache.get(key);
        if (cached != null) {
            return cached;
        }
        
        // 执行方法
        Object result = joinPoint.proceed();
        
        // 存入缓存
        cache.put(key, result);
        
        return result;
    }
    
    @After("@annotation(cacheEvict)")
    public void evictCache(JoinPoint joinPoint, CacheEvict cacheEvict) {
        String cacheName = cacheEvict.value();
        Cache cache = cacheManager.getCache(cacheName);
        
        if (cacheEvict.allEntries()) {
            cache.clear();
        } else {
            String key = generateKey(joinPoint);
            cache.evict(key);
        }
    }
}

5. 事务管理

java
@Aspect
@Component
public class TransactionalAspect {
    
    @Inject
    private TransactionManager transactionManager;
    
    @Around("@annotation(transactional)")
    public Object manageTransaction(ProceedingJoinPoint joinPoint, Transactional transactional) throws Throwable {
        TransactionDefinition definition = new TransactionDefinition();
        definition.setPropagation(transactional.propagation());
        definition.setIsolation(transactional.isolation());
        definition.setReadOnly(transactional.readOnly());
        
        TransactionStatus status = transactionManager.getTransaction(definition);
        
        try {
            Object result = joinPoint.proceed();
            transactionManager.commit(status);
            return result;
        } catch (Throwable e) {
            if (shouldRollback(e, transactional)) {
                transactionManager.rollback(status);
            } else {
                transactionManager.commit(status);
            }
            throw e;
        }
    }
}

最佳实践

1. 保持切面简单

java
// ✅ 推荐:简单的切面
@Aspect
@Component
public class SimpleLoggingAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        logger.info("调用: {}", joinPoint.getSignature().getName());
    }
}

// ❌ 不推荐:复杂的切面
@Aspect
@Component
public class ComplexAspect {
    // 太多逻辑,难以维护
}

2. 使用自定义注解

java
// ✅ 推荐:使用注解标记
@Timed
public User findById(Long id) {
    return userRepository.findById(id);
}

// ❌ 不推荐:硬编码切入点
@Pointcut("execution(* com.example.service.UserService.findById(..))")

3. 注意性能影响

java
// ✅ 推荐:只在需要的地方使用
@Around("@annotation(Timed)")
public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
    // ...
}

// ❌ 不推荐:拦截所有方法
@Around("execution(* *.*(..))")
public Object interceptAll(ProceedingJoinPoint joinPoint) throws Throwable {
    // 性能影响大
}

4. 处理异常

java
@Around("@annotation(Retry)")
public Object retry(ProceedingJoinPoint joinPoint) throws Throwable {
    try {
        return joinPoint.proceed();
    } catch (Throwable e) {
        // 正确处理异常
        logger.error("执行失败", e);
        throw e;  // 重新抛出
    }
}

5. 避免循环调用

java
// ❌ 不推荐:可能导致循环
@Aspect
@Component
public class BadAspect {
    
    @Inject
    private SomeService someService;  // 这个服务也被切面拦截
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        someService.doSomething();  // 可能导致循环
        return joinPoint.proceed();
    }
}

常见问题

Q: AOP 对性能有影响吗?

A: 有轻微影响,但通常可以忽略。建议:

  • 只在需要的地方使用
  • 避免在高频方法上使用复杂切面
  • 使用 @annotation 而不是 execution(*.*(..))

Q: 为什么我的切面没有生效?

A: 检查以下几点:

  1. 切面类是否有 @Aspect@Component 注解
  2. 切入点表达式是否正确
  3. 目标类是否是容器管理的组件
  4. 是否是自调用(同一个类内部调用)

Q: 如何调试切面?

A:

properties
logging.level.com.lcgyl.framework.core.aop=DEBUG

下一步

Released under the Apache License 2.0