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 中,多个通知的执行顺序遵循以下规则:
同一切面类中:
- 默认按照通知类型的自然调用顺序执行:
@Around → @Before → 目标方法 → @AfterReturning/@AfterThrowing → @After → @Around
- 若存在多个同类型通知(例如多个
@Before
),则按照方法名的字母顺序执行。 - 在
@Before
中,方法名字母顺序越小越先执行;在@After
中,方法名字字母顺序越小越后执行。
不同切面类之间:
-
默认按类名的字母顺序执行,规则和 1 中相同。
-
可通过
@Order
注解或实现Ordered
接口来控制优先级。 -
@Order
数值越小,优先级越高(即越早执行@Before
,越晚执行@After
)。
例如:
@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);
}
ProceedingJoinPoint
可以执行目标方法,而JoinPoint
不可以,前者是后者的子类。