21 January 2017

Spring AOP 适合把日志、耗时统计等横切逻辑放到业务方法之外。关键是知道切面拦截哪个方法、何时执行,以及如何保留原方法的返回值和异常。

一个记录耗时的切面

项目引入与 Spring 版本匹配的 AOP 依赖。Spring Boot 可以使用 spring-boot-starter-aop;普通 Spring 应开启相应自动代理配置,并让切面被组件扫描。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimingAspect {
    @Around("execution(public * com.example.service..*(..))")
    public Object measure(ProceedingJoinPoint point) throws Throwable {
        long start = System.nanoTime();
        try {
            return point.proceed();
        } finally {
            System.out.println(point.getSignature() + ": "
                    + (System.nanoTime() - start) + " ns");
        }
    }
}

切点包名按项目替换。proceed 执行业务方法,返回值原样传回;finally 在正常返回或异常退出时都会执行。实际系统应使用合适日志,避免输出全部参数。

通知类型表达不同时间点

Before 在调用前执行;AfterReturning 只处理正常返回;AfterThrowing 处理异常;After 类似 finally;Around 控制整个调用。void 方法没有可绑定的正常返回值,不能强行用 String 参数接收。

通常应保留异常继续向上传播,不捕获后返回 null 假装成功,也不在语义不明时多次调用 proceed。

代理边界决定哪些调用生效

Spring AOP 通常依赖代理。对象内部通过 this 调用另一个方法,往往不会再次经过代理;自己 new 出的对象也不会因为注解就自动被拦截。final、private 方法与接口代理或类代理各有限制。

从 Spring 容器取得 Bean,通过外部调用验证正常和异常路径。直接调用切面方法,不能证明业务调用真正经过代理。

参考:Spring AOP。





blog comments powered by Disqus
Fork me on GitHub