


Spring AOP

切面优先级**@Order**
当我们在一个项目中,定义了多个切面类时,并且这些切面类的多个切入点都匹配到了同一个目标方法
。
当目标方法运行
的时候,这些切面类中的通知方法都会执行
,那么这几个通知方法的执行顺序
是什么样的呢? 我们还是通过程序来求证:
为了测试切面优先级机制,我们先定义多个切面类 AspectDemo:

为防止干扰,我们把 AspectDemo1 这个切面先去掉(把 @Component 注解去掉就可以)。

为了测试简单化,我们在新增的测试切面类中,只写了 @Before 和 @After 两个通知:


运行程序,访问接口:

观察日志:

通过上述程序的运行结果,可以看出:

存在多个切面类
时,默认按照切面类的类名字母
排序:
@Before
通知:字母排名靠前的先执行
。@After
通知:字母排名靠前的后执行
。
但这种方式不方便管理,我们的类名更多还是具备一定含义的。
Spring 给我们提供了一个新的注解,来控制这些切面通知的执行顺序:@Order
,使用方式如下:
java
@Order(3)
public class AspectDemo2 {
}
@Order(2)
public class AspectDemo3 {
}
@Order(1)
public class AspectDemo4 {
}

重新运行程序,访问接口 http://127.0.0.1:8080/test/t2

观察日志:

切面类的多个切入点都匹配到了同一个目标方法的情况下,调用切点的优先级可以被 @Order() 注解调整:

通过上述程序的运行结果,得出结论:
@Order
注解标识的切面类
,执行顺序如下:
@Before
通知:数字越小
先执行。@After
通知:数字越大
先执行。
@Order
控制切面
的优先级
,先执行优先级较高的切面
,再执行优先级较低的切面
,最终执行目标方法
。

切点表达式
上面的代码中,我们一直在使用切点表达式来描述切点。下面我们来介绍一下切点表达式的语法。
切点表达式常见有两种表达方式:
-
execution(R )
:根据方法签名
来匹配。 -
@annotation(R )
:根据注解
匹配。
execution****表达式
execution() 是最常用的切点表达式,用来匹配方法,语法为:
execution(<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)
其中:访问修饰符
和异常
可以省略。

切点表达式支持通配符表达:
符号 | 描述 | 详细说明 |
---|---|---|
* |
匹配任意字符,只匹配一个元素 (返回类型、包、类名、方法、方法参数) | 包名:使用* 表示任意包(一层包使用一个 *) 类名:使用* 表示任意类 返回值:使用* 表示任意返回值类型 方法名:使用* 表示任意方法 参数:使用* 表示一个任意类型的参数 |
.. |
匹配多个连续的任意符号, 可以匹配任意层级的包, 或者任意类型,任意个数的参数 | 包名:标识此包以及此包下的所有子包 参数:表示任意个任意类型的参数 |
切点表达式示例:
TestController
下的 public 修饰
,返回类型为 String ,方法名为 t1,无参方法。
java
execution(public String com.example.demo.controller.TestController.t1())
省略访问修饰符
。
java
execution(String com.example.demo.controller.TestController.t1())
匹配所有返回类型
。
java
execution(* com.example.demo.controller.TestController.t1())
匹配 TestController 下的所有无参方法
。
java
execution(* com.example.demo.controller.TestController.*())
匹配 TestController 下的所有方法
。
java
execution(* com.example.demo.controller.TestController.*(..))
匹配 controller 包下所有的类
的所有方法
。
java
execution(* com.example.demo.controller.*.*(..))
匹配所有包
下面的 TestController。
java
execution(* com..TestController.*(..))
匹配 com.example.demo 包下
,子孙包下
的所有类
的所有方法
。
java
execution(* com.example.demo..*(..))
@annotation
execution 表达式
更适用于有规则的
,如果我们要匹配多个无规则的方法
呢;
比如:TestController
中的t1()
和 UserController
中的u1()
这两个方法:

这个时候我们使用 execution
这种切点表达式
来描述就不是很方便了。
我们可以借助自定义注解
的方式以及另一种切点表达式 @annotation
来描述这一类的切点。
实现步骤:
-
编写
自定义注解
。 -
使用
@annotation 表达式
来描述切点
。 -
在
连接点的方法上添加自定义注解
。
自定义注解**@MyAspect**
创建一个注解类(和创建 Class 文件一样的流程,选择 Annotation 就可以了)。

创建好一个注解后,我们定义这个注解的作用目标和作用生命周期:

@Target
@Target
标识了 Annotation 所修饰的对象范围
,即该注解可以用在什么地方。
取值 | 描述 |
---|---|
ElementType.TYPE |
用于描述类、接口(包括注解类型)或 enum 声明。 |
ElementType.METHOD |
描述方法。 |
ElementType.PARAMETER |
描述参数。 |
ElementType.TYPE_USE |
可以标注任意类型。 |
@Retention
@Retention
指 Annotation 被保留的时间长短
,标明注解的生命周期
。
@Retention
的取值有三种:
保留策略 | 描述 | 特点 | 示例 |
---|---|---|---|
RetentionPolicy.SOURCE |
注解仅存在于源代码中,编译成字节码后会被丢弃。 | - 运行时无法获取注解信息 - 只能在编译时使用 - 优化编译性能和字节码大小 | @SuppressWarnings @Data @Slf4j (Lombok注解) |
RetentionPolicy.CLASS |
注解存在于源代码和字节码中,但在运行时会被丢弃。 | - 编译时和字节码中可以通过反射获取注解信息 - 运行时无法获取注解信息 - 适用于框架和工具 | 通常用于一些框架和工具的注解 |
RetentionPolicy.RUNTIME |
注解存在于源代码、字节码和运行时中。 | - 编译时、字节码和运行时都可以通过反射获取注解信息 - 适用于需要在运行时处理的注解 | @Controller @ResponseBody (Spring注解) |
切面类
接下来定义一个切面类,用于实现我们自定义的注解 @MyAspect 的逻辑,切面类代码如下:

类名为 TimeRecord
,表示这个类的逻辑是用于记录方法执行的时间;
@annotation(com.bit.springaopdemo.aspect.MyAspect)
:表示使用 @annotation
切点表达式定义切点
,只对 @MyAspect
生效:

注掉切面类 AspectDemo
的 @Compenont
注解,避免干扰 TimeAspect() + @Around
组成的切面:

切面类 TimeRecord
代码:
java
@Aspect
@Component
@Slf4j
public class TimeRecord {
@Around("@annotation(com.bit.springaopdemo.aspect.MyAspect)")
public Object TimeAspect(ProceedingJoinPoint pjp) {
log.info("目标方法执行前.......");
Object result = null;
try {
result = pjp.proceed();
} catch (Throwable e) {
log.error("do Around throwing");
throw new RuntimeException(e);
}
log.info("目标方法执行后.......");
return result;
}
}
添加自定义注解
在 TestController 中的 t1() 和 UserController 中的 u1() 这两个方法上添加自定义注解 @MyAspect,其他方法不添加。

运行程序,测试接口:

使用 @annotation
切面表达式,不但可以拦截我们自定义的注解 @MyAspect,也可以拦截一些 Spring 封装好的注解,如 @RequestMapping;

只要有连接点(目标方法)使用了 @RequestMapping 注解,就会先执行 @annotation 声明下的通知(方法具体逻辑)
- 此路是我开,此树是我栽(
@annotation(@m)
)狗(连接点(带@m))
过了都得挨两耳勺儿(通知)
Spring AOP****的实现方式(常见面试题)
面试官:谈谈你对IOC和AOP的理解及AOP四种实现方式通俗易懂]-腾讯云开发者社区-腾讯云

