【基础系列】AOP之拦截优先级详解

文章目录
  1. I. 统一切面,不同类型ddvice优先级
    1. 1. case设计
    2. 2. 测试
    3. II. 同一切面,同一类型切面
    4. 1. case设计
    5. 2. 测试
    6. 3. Order注解尝试
    7. 4. 小结
    8. III. 不同切面,相同类型的advice
    9. 1. case设计
    10. 2. 测试
    11. 3. 小结
  2. IV. 不同切面,不同advice顺序
    1. 1. case设计
    2. 2. 测试
  3. V. 小结
    1. 1. 不同advice之间的优先级顺序
    2. 2. 统一切面中相同advice
    3. 3. 不同切面优先级
    4. 4. 不同切面advice执行顺序
  4. VI. 其他
    1. 0. 项目
    2. 1. 一灰灰Blog
    3. 2. 声明
    4. 3. 扫描关注

前面两篇分别介绍了AOP的基本使用姿势和一些高级特性,当时还遗留了一个问题没有说明,即不同的advice,拦截同一个目标方法时,优先级是怎样的,本篇博文将进行详细分析

  • 同一个切面中,不同类型的advice的优先级
  • 同一个切面中,同一种类型的advice优先级
  • 不同切面中,同一类型的advice优先级
  • 不同切面中,不同类型的advice优先级

I. 统一切面,不同类型ddvice优先级

在不分析源码的前提下,也只能通过实际的case来看优先级问题了,我们现在设计一下使用实例,通过输出结果来看对应的优先级

1. case设计

首先创建被拦截的bean: com.git.hui.boot.aop.order.InnerDemoBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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之基本使用姿势小结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@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,使用还是比较简单的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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);
}
}

看下上面执行的输出结果

1
2
3
4
5
6
7
8
9
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执行的先后顺序如下

IMAGE

II. 同一切面,同一类型切面

正常来讲,拦截一个方法时,统一类型的切面逻辑都会写在一起,那这个case有什么分析的必要呢?

在我们实际的使用中,同一类型的advice拦截同一个方法的可能性还是很高的,why? 因为多个advice有自己定义的拦截规则,它们之间并不相同,但可能存在交集,比如我们在上面的切面中,再加一个拦截注解的before advice

1. case设计

依然是上面的InnerDemoBean,方法上加一个自定义注解

1
2
3
4
5
6
7
8
9
10
11
@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

1
2
3
4
@Before("@annotation(AnoDot)")
public void doAnoBefore(JoinPoint joinPoint) {
System.out.println("dp AnoBefore");
}

2. 测试

再次执行前面的case,然后看下输出结果如下

1
2
3
4
5
6
7
8
9
10
11
12
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,然后我们预估输出结果应该是

1
do AnoBefore > doBefore > doXBefore

额外添加一个

1
2
3
4
@Before("@annotation(AnoDot)")
public void doXBefore(JoinPoint joinPoint) {
System.out.println("dp XBefore");
}

接着就是输出结果如下,和我们预期一致

IMAGE

3. Order注解尝试

我们知道有个Order注解可以来定义一些优先级,那么把这个注解放在advice方法上,有效么?实际尝试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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");
}

如果注解有效,我们预期输出结果如下

1
do Before > do AnoBefore > do XBefore

然后再次执行,看下输出结果是否和我们预期一样

IMAGE

4. 小结

同一个切面中,相同的类型的advice,优先级是根据方法命名来的,加@Order注解是没有什么鸟用的,目前也没有搜索到可以调整优先级的方式

III. 不同切面,相同类型的advice

如果说上面这种case不太好理解为啥会出现的话,那么这个可能就容易理解多了;毕竟一个切面完成一件事情,出现相同的advice就比较常见了;

比如spring mvc中,我们通常会实现的几个切面

  • 一个before advice的切面,实现输出请求日志
  • 一个before advice的切面,实现安全校验(这种其实更常见的是放在filter/intercept中)

1. case设计

现在就需要再加一个切面,依然以before advice作为case

1
2
3
4
5
6
7
8
@Aspect
@Component
public class AnotherOrderAspect {
@Before("@annotation(AnoDot)")
public void doBefore() {
System.out.println("in AnotherOrderAspect before!");
}
}

2. 测试

接下来看测试输出结果如下图

IMAGE

发现了一个有意思的事情了,AnotherOrderAspect切面的输出,完全在OrderAspect切面中所有的advice之前,接着我们再次尝试使用@Order注解来试试,看下会怎样

1
2
3
4
5
6
7
8
9
10
11
@Order(0)
@Component
@Aspect
public class OrderAspect {
}

@Aspect
@Order(10)
@Component
public class AnotherOrderAspect {
}

如果顺序有关,我们预期的输出结果应该是

1
do AnoBefore > do Before > doXBefore > do AnotherOrderAspect before!

实际测试输出如下,和我们预期一致

IMAGE

3. 小结

从上面的测试来看,不同的切面,默认顺序实际上是根据切面的命令来的;

  • A切面中的advice会优先B切面中同类型的advice
  • 我们可以通过 Order 注解来解决不同切面的优先级问题,依然是值越小,优先级越高

IV. 不同切面,不同advice顺序

其实前面的case已经可以说明这个问题了,现在稍稍丰富一下AnotherOrderAspect,看下结果

1. case设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@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. 测试

看下执行后的输出结果

IMAGE

假设A切面优先级高于B切面,那么我们执行先后顺序如下

IMAGE

V. 小结

本篇内容有点多,针对前面的测试以及结果分析,给出一个小结,方便直接获取最终的答案

1. 不同advice之间的优先级顺序

1
around 方法执行前代码  >  before > 方法执行 > around方法执行后代码 > after > afterReturning/@AfterThrowing

2. 统一切面中相同advice

统一切面中,同类型的advice的优先级根据方法名决定,暂未找到可以控制优先级的使用方式

3. 不同切面优先级

不同切面优先级,推荐使用 @Order注解来指定,数字越低,优先级越高

4. 不同切面advice执行顺序

优先级高的切面中的advice执行顺序会呈现包围优先级低的advice的情况,更直观的先后顺序,推荐看第四节的顺序图,更加清晰明了

VI. 其他

0. 项目

1. 一灰灰Blog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

3. 扫描关注

一灰灰blog

QrCode

知识星球

goals


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到