Skip to content

Spring AOP

发表于: 时间 19:00
加载中...

1. Spring AOP 示例注解解释

@Before

在目标方法执行前运行。常用于记录日志、参数校验、权限控制等。 当匹配到定义的切点表达式后,Spring 会在目标方法调用前先执行该方法。

@Around

环绕通知,包裹目标方法的整个执行过程。通过 ProceedingJoinPoint.proceed() 显式调用目标方法,可以在调用前后添加自定义逻辑。 这是最灵活的通知类型,可以控制方法是否执行、修改参数或处理返回值。

@After

在目标方法执行结束后执行,无论是否发生异常都会触发。类似于 finally 代码块,常用于资源释放、通用日志等场景。

@AfterReturning

在目标方法正常返回后执行,如果目标方法抛出了异常则不会调用。常用于处理返回结果或输出执行成功日志。

@AfterThrowing

当目标方法抛出异常时执行。可用于记录异常信息、发送报警、异常统计等。

@Pointcut

@Pointcut 用于抽取切点表达式,即将原本写在每个注解中的 execution(...) 规则提取出来,单独定义成一个可复用的方法。 这样在多个通知中就可以直接通过方法名来引用,避免重复书写表达式,使切面更加清晰易维护。

一般定义方式如下:

@Pointcut("execution(* top.mygld.demo.service.impl..*(..))")
public void serviceMethods() {}

然后在其他通知中直接使用:

@Before("serviceMethods()")
@After("serviceMethods()")

2. Spring AOP 代码示例(使用 @Pointcut 抽取)

package top.mygld.demo.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class TestAspect {

    // 抽取切点表达式,匹配 service.impl 包下所有方法
    @Pointcut("execution(* top.mygld.demo.service.impl..*(..))")
    public void serviceMethods() {}

    @Before("serviceMethods()")
    public void before() {
        log.info("before ....");
    }

    @Around("serviceMethods()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("around ... before ....");
        Object result = joinPoint.proceed();
        log.info("around ... after ....");
        return result;
    }

    @After("serviceMethods()")
    public void after() {
        log.info("after ....");
    }

    @AfterReturning("serviceMethods()")
    public void afterReturning() {
        log.info("afterReturning ....");
    }

    @AfterThrowing("serviceMethods()")
    public void afterThrowing() {
        log.info("afterThrowing ....");
    }

}

这些注解的执行顺序大致为: 当方法正常执行时:@Around → @Before → 方法执行 → @AfterReturning → @After → @Around 当方法抛出异常时:@Around → @Before → 方法执行 → @AfterThrowing → @After → @Around

通过这些注解的组合,可以灵活地在不同阶段切入业务逻辑,实现统一的日志、监控或安全控制。 而使用 @Pointcut 则让多个通知共享同一套匹配规则,结构更清晰、可维护性更高。

当然如果每个方法要单独配置不同的切入点表达式,就可以分开写,例如:

@AfterReturning("execution(* top.mygld.demo.dao.impl..*(..))")
public void afterReturning(){
    log.info("afterReturning ....");
}

@AfterThrowing("execution(* top.mygld.demo.service.impl..*(..))")
public void afterThrowing(){
    log.info("afterThrowing ....");
}

3. 多个通知的执行顺序

在 Spring AOP 中,多个通知的执行顺序遵循以下规则:

同一切面类中

不同切面类之间

例如:

@Aspect
@Component
@Order(5)
public class LogAspect1 {}

@Aspect
@Component
@Order(7)
public class LogAspect2 {}

执行过程概览

外层切面 @Around

  内层切面 @Around

    @Before(由外到内)

     目标方法执行

    @AfterReturning / @AfterThrowing(由内到外)

    @After(由内到外)

  内层 @Around 结束

外层 @Around 结束

这一机制确保在存在多个切面或通知时,AOP 的执行顺序是可预测且可精确控制的。

4. 切入点表达式

execution

execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)

带 ? 的表示可以省略的部分。

1.访问修饰符:可省略(比如:public、protected) 2.包名.类名:可省略(但是不建议) 3.throws异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

*:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的参数,也可以通配包、类、方法名的一部分

execution(* top.*.service.*.update*(*))

.. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

execution(* top.mygld..UserService.*(..))

annotation

根据注解去匹配方法,只对添加对应注解的方法有效,例如我们先创建一个注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation {
}

然后把注解加在要匹配的方法上:

@LogOperation
@Override
public List<User> findAll() {
    return userDao.findAll();
}

然后在切面类中使用 @annotation(注解引用)

@Before("@annotation(top.mygld.demo.anno.LogOperation)")
public void before(){
    log.info("before");
}

这样在 idea 中可以点击前面的 m 图标,便可验证匹配成功。

5. 连接点

Spring 中用 JoinPoint 抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint。 对于其它四种通知,获取连接点信息只能使用 JoinPoint,它是 ProceedingJoinPoint 的父类型。

下面以 @Before 进行演示:

@Before("@annotation(top.mygld.demo.anno.LogOperation)")
public void before(JoinPoint jp){
    log.info("before");
    //1. 获取目标对象
    Object target = jp.getTarget();
    log.info("target: {}", target);
        
    //2. 获取目标类
    String name = target.getClass().getName();
    log.info("name: {}", name);
        
    //3. 获取目标方法
    String methodName = jp.getSignature().getName();
    log.info("methodName: {}", methodName);
        
    //4. 获取目标方法参数
    Object[] args = jp.getArgs();
    log.info("args: {}", args);
}

上一篇文章
第三方 Bean 注入 IoC 容器
下一篇文章
volatile 关键字