跳转到内容

Spring环绕通知(Around Advice)

来自代码酷

模板:Note

Spring环绕通知(Around Advice)[编辑 | 编辑源代码]

环绕通知是Spring AOP中最强大的通知类型,它允许开发者**完全控制目标方法的执行过程**。与其他通知不同,环绕通知可以决定是否执行目标方法、修改参数值或返回值,甚至捕获和处理异常。

核心特性[编辑 | 编辑源代码]

  • 完全控制流程:通过ProceedingJoinPoint决定是否调用目标方法
  • 参数与返回值处理:可修改方法参数和返回值
  • 异常处理:能捕获并转换异常
  • 性能监控:常用于记录方法执行时间

语法结构[编辑 | 编辑源代码]

基本语法示例:

@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    // 前置逻辑
    Object result = joinPoint.proceed(); // 调用目标方法
    // 后置逻辑
    return result;
}

关键组件解析[编辑 | 编辑源代码]

组件 说明
@Around 声明环绕通知的注解
ProceedingJoinPoint 提供访问目标方法信息的接口
proceed() 执行目标方法的核心方法

完整示例[编辑 | 编辑源代码]

以下是一个包含性能监控和异常处理的完整案例:

@Aspect
@Component
public class MonitoringAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object monitorMethod(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        
        try {
            // 可在此修改参数
            Object[] args = pjp.getArgs();
            if (args.length > 0 && args[0] instanceof String) {
                args[0] = ((String) args[0]).trim();
            }
            
            Object result = pjp.proceed(args);
            
            // 可在此修改返回值
            if (result instanceof List) {
                ((List<?>) result).removeIf(Objects::isNull);
            }
            
            return result;
        } catch (Exception e) {
            // 异常转换示例
            throw new ServiceException("操作失败: " + e.getMessage(), e);
        } finally {
            long duration = System.currentTimeMillis() - start;
            System.out.printf("方法 %s 执行耗时: %dms%n", 
                pjp.getSignature().getName(), duration);
        }
    }
}

执行流程[编辑 | 编辑源代码]

sequenceDiagram participant Client participant Aspect participant Target Client->>Aspect: 调用代理方法 Aspect->>Target: 前置处理 → 条件性调用proceed() Target-->>Aspect: 返回结果/异常 Aspect->>Client: 返回处理后的结果/转换后的异常

实际应用场景[编辑 | 编辑源代码]

1. 事务管理[编辑 | 编辑源代码]

通过环绕通知实现声明式事务:

@Around("@annotation(transactional)")
public Object manageTransaction(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
        Object result = pjp.proceed();
        transactionManager.commit(status);
        return result;
    } catch (Exception e) {
        transactionManager.rollback(status);
        throw e;
    }
}

2. 缓存控制[编辑 | 编辑源代码]

实现缓存-穿透保护模式:

@Around("@annotation(cacheable)")
public Object checkCache(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
    String key = generateCacheKey(pjp);
    Object cached = cache.get(key);
    if (cached != null) return cached;
    
    Object result = pjp.proceed();
    if (result != null) {
        cache.put(key, result, cacheable.expire());
    }
    return result;
}

高级技巧[编辑 | 编辑源代码]

嵌套调用处理[编辑 | 编辑源代码]

当多个环绕通知作用于同一方法时,执行顺序由aspect的@Order注解决定:

@Aspect
@Order(1)
public class LoggingAspect {
    @Around("execution(* com..*(..))")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        // 日志逻辑
        return pjp.proceed();
    }
}

@Aspect
@Order(2)
public class SecurityAspect {
    @Around("execution(* com..*(..))")
    public Object checkPermission(ProceedingJoinPoint pjp) throws Throwable {
        // 权限检查
        return pjp.proceed();
    }
}

性能优化建议[编辑 | 编辑源代码]

  • 避免在环绕通知中执行耗时操作
  • 对高频调用方法考虑使用@Aroundif()条件
  • 使用joinPoint.getArgs()的缓存结果

常见问题[编辑 | 编辑源代码]

页面模块:Message box/ambox.css没有内容。

问题 解决方案
忘记调用proceed() 使用IDE模板确保基本结构正确
修改不可变参数 对String等不可变对象需要特殊处理
异常处理覆盖 确保不会意外吞没关键异常

数学表达[编辑 | 编辑源代码]

环绕通知的执行时间公式: Ttotal=Tbefore+Tmethod+Tafter 其中:

  • Tbefore = 前置处理时间
  • Tmethod = 目标方法执行时间
  • Tafter = 后置处理时间