注解式 AOP:@Aspect 与五类通知

引言

上一章建立了 AOP 术语。Spring 支持 AspectJ 风格的注解声明:用 @Aspect 标注切面类,用 @Pointcut 定义切点,再用五个通知注解挂在方法上,配一个开关即可运行。本章给出完整可跑的示例。

核心概念

  • 开启方式:@Configuration 类上标 @EnableAspectJAutoProxy(纯 Spring);Spring Boot 引入 spring-boot-starter-aop 后自动开启(以官方文档为准)。
  • 切面类:@Aspect + 普通 Bean(@Component@Bean 注册)。
  • 切点表达式:最常用 execution(修饰符? 返回类型 类名.方法名(参数)),如 execution(public * com.example..service.*.*(..)) 表示任意返回值、service 包下所有类的所有方法。Spring AOP 支持的指示器包括 execution、within、target、args、this、@within、@target、@args、@annotation、bean,可用 && || ! 组合(以官方文档为准)。
  • 通知注解与参数:
    • @Before("切点")
    • @AfterReturning(pointcut, returning = "result") 接收返回值;
    • @AfterThrowing(pointcut, throwing = "ex") 接收异常;
    • @After:finally 语义;
    • @Around:方法参数为 ProceedingJoinPointpjp.proceed() 放行并可改写返回值/捕获异常。
  • 具名切点:@Pointcut("execution(...)") private void svc() {},其他通知引用方法名即可复用。

代码示例

@Aspect
@Component
public class ServiceMonitor {
    // 切点:service 包下任意类的任意方法
    @Pointcut("execution(* com.example..service.*.*(..))")
    private void serviceMethod() {}

    @Before("serviceMethod()")
    public void before() { System.out.println("调用前:开始计时/检查权限"); }

    @AfterReturning(pointcut = "serviceMethod()", returning = "result")
    public void afterReturn(Object result) { System.out.println("正常返回: " + result); }

    @AfterThrowing(pointcut = "serviceMethod()", throwing = "ex")
    public void afterThrow(Exception ex) { System.out.println("抛出异常: " + ex.getMessage()); }

    @After("serviceMethod()")
    public void after() { System.out.println("无论成败都执行:收尾"); }

    @Around("serviceMethod()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        Object ret = pjp.proceed();            // 放行目标方法
        System.out.println("耗时(ms): " + (System.nanoTime() - start) / 1_000_000);
        return ret;
    }
}

注意点

  • @Aspect 类本身不参与自身切点匹配(避免自代理死循环),别在切面方法里再调自身切中方法。
  • 多个切面作用于同一方法时用 @Order(1)/@Order(2) 控制执行顺序:数字小的先执行(Before 顺序与 Around/After 的包裹方向不同,以官方文档为准)。
  • @Around 忘记 proceed() 等于拦截不放行;proceed() 抛异常要按需转换,别擅自吞掉破坏事务语义。
  • 返回/异常绑定名必须与通知方法参数名一致,否则注入不了。
  • 切点尽量收敛(包/注解限定),execution(* *..*(..)) 全量匹配会拖累性能并引入意外副作用。

小结

@EnableAspectJAutoProxy 开开关,@Aspect + @Pointcut 定位置,五个通知注解定时机;@Around 最灵活、其余四个语义单一。先写窄切点、多打日志验证命中范围,再逐步加逻辑,是上手 AOP 最稳的路径。

笔记加载中…