190301-SpringBoot 基础篇 AOP 之基本使用姿势小结
190302-SpringBoot 基础篇 AOP 之高级使用技能
前面两篇分别介绍了 AOP 的基本使用姿势和一些高级特性, 当时还遗留了一个问题没有说明, 即不同的 advice, 拦截同一个目标方法时, 优先级是怎样的, 本篇博文将进行详细分析
同一个切面中, 不同类型的 advice 的优先级
同一个切面中, 同一种类型的 advice 优先级
不同切面中, 同一类型的 advice 优先级
不同切面中, 不同类型的 advice 优先级
I. 统一切面, 不同类型 ddvice 优先级
在不分析源码的前提下, 也只能通过实际的 case 来看优先级问题了, 我们现在设计一下使用实例, 通过输出结果来看对应的优先级
1. case 设计
首先创建被拦截的 bean: com.Git.hui.boot.aop.order.InnerDemoBean
- @Component
- public class InnerDemoBean {
- public String print() {
- try {
- System.out.println("in innerDemoBean start!");
- String rans = System.currentTimeMillis() + "|" + UUID.randomUUID();
- System.out.println(rans);
- return rans;
- } finally {
- System.out.println("in innerDemoBean over!");
- }
- }
- }
接下来写一个切面, 里面定义我们常见的各种 advice
对于 aop 的使用, 有疑问的可以参考: 190301-SpringBoot 基础篇 AOP 之基本使用姿势小结
- @Component
- @Aspect
- public class OrderAspect {
- @Pointcut("execution(public * com.git.hui.boot.aop.order.*.*())")
- public void point() {
- }
- @Before(value = "point()")
- public void doBefore(JoinPoint joinPoint) {
- System.out.println("do before!");
- }
- @After(value = "point()")
- public void doAfter(JoinPoint joinPoint) {
- System.out.println("do after!");
- }
- @AfterReturning(value = "point()", returning = "ans")
- public void doAfterReturning(JoinPoint joinPoint, String ans) {
- System.out.println("do after return:" + ans);
- }
- @Around("point()")
- public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
- try {
- System.out.println("do in around before");
- return joinPoint.proceed();
- } finally {
- System.out.println("do in around over!");
- }
- }
- }
2. 测试
使用 SpringBoot 的项目进行测试 aop, 使用还是比较简单的
- @SpringBootApplication
- public class Application {
- private InnerDemoBean innerDemoBean;
- public Application(InnerDemoBean innerDemoBean) {
- this.innerDemoBean = innerDemoBean;
- this.innerDemoBean();
- }
- private void innerDemoBean() {
- System.out.println("result:" + innerDemoBean.print());
- }
- public static void main(String[] args) {
- SpringApplication.run(Application.class);
- }
- }
看下上面执行的输出结果
- do in around before
- do before!
- in innerDemoBean start!
- 1552219604035|e9a31f44-6a31-4485-806a-834361842ce1
- in innerDemoBean over!
- do in around over!
- do after!
- do after return: 1552219604035|e9a31f44-6a31-4485-806a-834361842ce1
- result: 1552219604035|e9a31f44-6a31-4485-806a-834361842ce1
从输出结果进行反推, 我们可以知道统一切面中, advice 执行的先后顺序如下
II. 同一切面, 同一类型切面
正常来讲, 拦截一个方法时, 统一类型的切面逻辑都会写在一起, 那这个 case 有什么分析的必要呢?
在我们实际的使用中, 同一类型的 advice 拦截同一个方法的可能性还是很高的, why? 因为多个 advice 有自己定义的拦截规则, 它们之间并不相同, 但可能存在交集, 比如我们在上面的切面中, 再加一个拦截注解的 before advice
1. case 设计
依然是上面的 InnerDemoBean, 方法上加一个自定义注解
- @AnoDot
- public String print() {
- try {
- System.out.println("in innerDemoBean start!");
- String rans = System.currentTimeMillis() + "|" + UUID.randomUUID();
- System.out.println(rans);
- return rans;
- } finally {
- System.out.println("in innerDemoBean over!");
- }
- }
然后加一个拦截注解的 advice
- @Before("@annotation(AnoDot)")
- public void doAnoBefore(JoinPoint joinPoint) {
- System.out.println("dp AnoBefore");
- }
2. 测试
再次执行前面的 case, 然后看下输出结果如下
- In NetAspect doAround before!
- do in around before
- dp AnoBefore
- do before!
- in innerDemoBean start!
- 1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0
- in innerDemoBean over!
- do in around over!
- do after!
- do after return: 1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0
- In NetAspect doAround over! ans: 1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0
- result: 1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0
我们主要看下两个 before, 发现 AnoBefore 在前面; 因此这里的一个猜测, 顺序就是根据方法命名的顺序来的, 比如我们再加一个 doXBefore, 然后我们预估输出结果应该是
do AnoBefore> doBefore> doXBefore
额外添加一个
- @Before("@annotation(AnoDot)")
- public void doXBefore(JoinPoint joinPoint) {
- System.out.println("dp XBefore");
- }
接着就是输出结果如下, 和我们预期一致
3. Order 注解尝试
我们知道有个 Order 注解可以来定义一些优先级, 那么把这个注解放在 advice 方法上, 有效么? 实际尝试一下
- @Order(1)
- @Before(value = "point()")
- public void doBefore(JoinPoint joinPoint) {
- System.out.println("do before!");
- }
- @Order(2)
- @Before("@annotation(AnoDot)")
- public void doAnoBefore(JoinPoint joinPoint) {
- System.out.println("dp AnoBefore");
- }
- @Order(3)
- @Before("@annotation(AnoDot)")
- public void doXBefore(JoinPoint joinPoint) {
- System.out.println("dp XBefore");
- }
如果注解有效, 我们预期输出结果如下
do Before> do AnoBefore> do XBefore
然后再次执行, 看下输出结果是否和我们预期一样
4. 小结
同一个切面中, 相同的类型的 advice, 优先级是根据方法命名来的, 加 @Order 注解是没有什么鸟用的, 目前也没有搜索到可以调整优先级的方式
III. 不同切面, 相同类型的 advice
如果说上面这种 case 不太好理解为啥会出现的话, 那么这个可能就容易理解多了; 毕竟一个切面完成一件事情, 出现相同的 advice 就比较常见了;
比如 spring mvc 中, 我们通常会实现的几个切面
一个 before advice 的切面, 实现输出请求日志
一个 before advice 的切面, 实现安全校验 (这种其实更常见的是放在 filter/intercept 中)
1. case 设计
现在就需要再加一个切面, 依然以 before advice 作为 case
- @Aspect
- @Component
- public class AnotherOrderAspect {
- @Before("@annotation(AnoDot)")
- public void doBefore() {
- System.out.println("in AnotherOrderAspect before!");
- }
- }
2. 测试
接下来看测试输出结果如下图
发现了一个有意思的事情了, AnotherOrderAspect 切面的输出, 完全在 OrderAspect 切面中所有的 advice 之前, 接着我们再次尝试使用 @Order 注解来试试, 看下会怎样
- @Order(0)
- @Component
- @Aspect
- public class OrderAspect {
- }
- @Aspect
- @Order(10)
- @Component
- public class AnotherOrderAspect {
- }
如果顺序有关, 我们预期的输出结果应该是
do AnoBefore> do Before> doXBefore> do AnotherOrderAspect before!
实际测试输出如下, 和我们预期一致
3. 小结
从上面的测试来看, 不同的切面, 默认顺序实际上是根据切面的命令来的;
A 切面中的 advice 会优先 B 切面中同类型的 advice
我们可以通过 Order 注解来解决不同切面的优先级问题, 依然是值越小, 优先级越高
IV. 不同切面, 不同 advice 顺序
其实前面的 case 已经可以说明这个问题了, 现在稍稍丰富一下 AnotherOrderAspect, 看下结果
1. case 设计
- @Aspect
- @Order(10)
- @Component
- public class AnotherOrderAspect {
- @Before("@annotation(AnoDot)")
- public void doBefore() {
- System.out.println("in AnotherOrderAspect before!");
- }
- @After("@annotation(AnoDot)")
- public void doAfter(JoinPoint joinPoint) {
- System.out.println("do AnotherOrderAspect after!");
- }
- @AfterReturning(value = "@annotation(AnoDot)", returning = "ans")
- public void doAfterReturning(JoinPoint joinPoint, String ans) {
- System.out.println("do AnotherOrderAspect after return:" + ans);
- }
- @Around("@annotation(AnoDot)")
- public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
- try {
- System.out.println("do AnotherOrderAspect in around before");
- return joinPoint.proceed();
- } finally {
- System.out.println("do AnotherOrderAspect in around over!");
- }
- }
- }
2. 测试
看下执行后的输出结果
假设 A 切面优先级高于 B 切面, 那么我们执行先后顺序如下
V. 小结
本篇内容有点多, 针对前面的测试以及结果分析, 给出一个小结, 方便直接获取最终的答案
1. 不同 advice 之间的优先级顺序
around 方法执行前代码 > before> 方法执行 > around 方法执行后代码 > after> afterReturning/@AfterThrowing
2. 统一切面中相同 advice
统一切面中, 同类型的 advice 的优先级根据方法名决定, 暂未找到可以控制优先级的使用方式
3. 不同切面优先级
不同切面优先级, 推荐使用 @Order 注解来指定, 数字越低, 优先级越高
4. 不同切面 advice 执行顺序
优先级高的切面中的 advice 执行顺序会呈现包围优先级低的 advice 的情况, 更直观的先后顺序, 推荐看第四节的顺序图, 更加清晰明了
VI. 其他
0. 项目
工程: https://github.com/liuyueyi/spring-boot-demo
module:
1. 一灰灰 Blog
一灰灰 Blog 个人博客 https://blog.hhui.top
一灰灰 Blog-Spring 专题博客 http://spring.hhui.top
一灰灰的个人博客, 记录所有学习和工作中的博文, 欢迎大家前去逛逛
2. 声明
尽信书则不如, 以上内容, 纯属一家之言, 因个人能力有限, 难免有疏漏和错误之处, 如发现 bug 或者有更好的建议, 欢迎批评指正, 不吝感激
微博地址: 小灰灰 Blog https://weibo.com/p/1005052169825577/home
QQ: 一灰灰 / 3302797840
3. 扫描关注
一灰灰 blog
知识星球
来源: https://juejin.im/post/5c87a625e51d450b662a122c