AOP进阶
2026年2月6日大约 4 分钟
AOP进阶
通知/增强类型
在前面AOP基础中,我们使用了@Around通知,这是环绕通知,可以在方法执行前后都进行增强。
- 通知注解用法:
@通知类型("切入点表达式")
@Pointcut-复用切入点表达式
@Pointcut注解,它用于定义切入点表达式,方便在多个通知中复用。
- 示例:
// 定义切入点表达式
@Pointcut("execution(* com.example.demo.service.UserService.*(..))")
public void userServicePt() {}
// 在通知中使用切入点表达式
@Before("userServicePt()")
public void logBefore() {
System.out.println("before...");
}除了@Around通知外,Spring AOP还提供了其他几种常用的通知类型,下面将逐一介绍。
@Around 环绕通知
- 作用:环绕通知,在目标方法执行前、后都被执行。
- 用法:使用
@Around注解,并通过ProceedingJoinPoint参数来控制目标方法的执行。 - 示例:
@Around("execution(* com.example.demo.service.UserService.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("around before...");
Object result = joinPoint.proceed(); // 执行目标方法
System.out.println("around after...");
return result;
}@Before 前置通知
- 作用:前置通知,在目标方法执行前被执行。
@After 后置通知
- 作用:后置通知,在目标方法执行后被执行,无论方法是否出现异常。
@AfterReturning 返回后通知
- 作用:返回后通知,在目标方法成功执行并返回结果后被执行,有异常则不执行。
@AfterThrowing 异常后通知
- 作用:异常后通知,在目标方法抛出异常后被执行。
注意
- 只有
@Around通知需要使用ProceedingJoinPoint.proceed()来执行目标方法,其他通知类型不需要。 - 此外
@Around方法返回值通常为Object,以便返回目标方法的结果。
通知顺序(通知优先级)
下面介绍在多个切面类中,通知的执行顺序规则:
- 在不同切面类中,默认按照切面类的类名字母顺序执行通知。
- 目标方法前的通知(如
@Before),按照字母升序执行。 - 目标方法后的通知(如
@After),按照字母降序执行。
- 目标方法前的通知(如
- 可以使用
@Order注解来指定切面类的优先级。- 目标方法前的通知,数字越小优先级越高,越先执行。
- 目标方法后的通知,数字越小优先级越高,越后执行。
提示
可以看到,多个切面时,通知的执行顺序和栈的先进后出类似。
切入点表达式
- 切入点表达式,是用来指定哪些方法需要被增强的规则。
- 常见形式有:
execution(...):根据方法的签名匹配。@annotation(...):根据注解匹配。
execution表达式
- execution主要根据方法的返回值、包名、类名、方法名和方法参数来匹配。
- 语法:
execution(访问修饰符? 返回值类型 包名.类名.?方法名(参数列表) throws 异常类型?)其中?表示可选项- 访问修饰符:如
public、private等,可以省略。 - 包名.类名:可省略,但不建议。
- throws 异常类型:可省略。
- 访问修饰符:如
- 示例:
// 匹配com.example.demo.service包下所有类的所有方法
@Pointcut("execution(* com.example.demo.service.*.*(..))")
public void serviceMethods() {}- 上面示例中出现了两个通配符
*和..:*:匹配单个元素,可以是任意的单个参数的返回值类型、类名或方法名等。..:匹配多个元素,可以是任意数量的参数类型,或者任意数量的包层级等。
注意
- 切入点表达式可以使用逻辑运算符
&&、||和!来组合多个表达式。 - 例如:
execution(* com.example.demo.service.*.*(..)) && !execution(* com.example.demo.service.UserService.*(..))
表示匹配service包下所有类的所有方法,但排除UserService类的所有方法。
补充说明
- 在业务方法命名时,应该规范,方便切入点表达式的匹配,如查询方法使用
find*、更新方法使用update*等。 - 描述切入点表达式时,应该基于接口而不是实现类,这样可以更好地解耦。
- 在满足业务需求的情况下,切入点表达式匹配范围应该尽量小。如包名匹配使用
*匹配单个包,而不是使用..匹配所有子包。
@annotation表达式
- @annotation主要根据方法上的注解来匹配。
- 语法:
@annotation(注解全类名) - 示例:
// 定义一个自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Myannotation {}// 在切面中使用@annotation表达式匹配带有@Myannotation注解的方法
@before("@annotation(com.example.demo.annotation.Myannotation)")
public void logBefore() {
System.out.println("before...");
}连接点 JoinPoint
在Spring中,可以使用JoinPoint接口来获取连接点(目标方法)的信息,比如目标类名,方法名,方法参数等。
- 对于
@Around通知,获取连接点信息只能使用ProceedingJoinPoint。 - 对于其他通知类型,获取连接点信息只能使用
JoinPoint,它是ProceedingJoinPoint的父接口。 - 示例(以环绕通知为例):
@Around("execution(* com.example.demo.service.UserService.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getTarget().getClass().getName(); // 获取目标类名
String methodName = joinPoint.getSignature().getName(); // 获取方法名
Object[] args = joinPoint.getArgs(); // 获取方法参数
Object result = joinPoint.proceed(); // 执行目标方法,只有在环绕通知中才有此方法
return result;
}JoinPoint常用方法:getTarget():获取目标对象。getSignature():获取方法签名对象(获取方法名需要调用getName()方法)。getArgs():获取方法参数数组。proceed():执行目标方法,仅在ProceedingJoinPoint中可用。