AOP 使用指南
Spring Framework

一、常用注解
| 注解 | 说明 |
|---|---|
| @Before | 前置通知, 在方法执行之前执行 |
| @After | 后置通知, 在方法执行之后执行 |
| @AfterRunning | 返回通知 在方法返回结果之后执行 |
| @AfterThrowing | 异常通知在方法抛出异常之后 |
| @Around | 环绕通知, 围绕着方法执行 |
二、切面表达式
| 注解 | 说明 |
|---|---|
| within | 拦截指定类及指定包下所有的类 |
| @within | 拦截被指定注解修饰的类 |
| this | 拦截指定的类型 |
| args | 拦截指定参数类型的方法 |
| @annotation | 拦截带指定注解的方法 |
| @args | 拦截方法入参被中@args指定的注解(入参只能有一个) |
| execution | 表达式详情见下文 |
三、API使用案例
3.1 within
a. API说明
- 精确匹配类名
- 模糊匹配包中所有的类
- 模糊匹配包中所有的带Impl后缀的
b. 目录
text
1└── WithinMatchProcessor
2 ├── AopWithinMatchProcessor.java
3 ├── CokeImpl.java
4 ├── Water.java
5 └── readme.mdc. 拦截代码
java
1@Aspect
2@Component
3public class AopWithinMatchProcessor {
4
5 /**
6 * 精确匹配类名
7 */
8 @Pointcut("within(spring.learning.aop.WithinMatchProcessor.Water)")
9 private void matchClassName() {
10 }
11
12 /**
13 * 模糊匹配包中所有的类
14 */
15 @Pointcut("within(spring.learning.aop.WithinMatchProcessor.*)")
16 private void matchAllClassFromPackage() {
17 }
18
19 /**
20 * 模糊匹配包中所有的带Impl后缀的
21 */
22 @Pointcut("within(spring.learning.aop.WithinMatchProcessor.*Impl)")
23 private void matchClassFromPackage() {
24 }
25
26
27 @Before("matchClassName()")
28 public void beforeMatchClassName() {
29 System.out.println("--------精确匹配类名-------");
30 }
31
32 @Before("matchAllClassFromPackage()")
33 public void beforeMatchAllClassFormPackage() {
34 System.out.println("--------模糊匹配包中所有的类-------");
35 }
36
37 @Before("matchClassFromPackage()")
38 public void beforeMatchClassFromPackage() {
39 System.out.println("--------模糊匹配包中所有的带Impl后缀的-------");
40 }
41
42
43}
443.2 @within
a. API说明
拦截被指定注解标注的类
b. 目录
text
1├── AnnotationWithinMatchProcessor
2│ ├── AopAnnotationWithinMatchProcessor.java
3│ ├── Log.java
4│ ├── Sprite.java
5│ └── readme.md
6c. 拦截代码
java
1@Log(tag = "SpriteLog")
2@Component
3public class Sprite {
4
5 public void drink() {
6 System.out.println("空参数");
7 }
8
9 public void drink(Integer age) {
10 System.out.println("age");
11 }
12
13
14 public String name() {
15 return "Sprite.name";
16 }
17
18 public void toCalculate() throws Exception {
19 System.out.println(0 / 0);
20 }
21}
22
23@Aspect
24@Component
25public class AopAnnotationWithinMatchProcessor {
26
27
28 /**
29 * 注意可以将注解,放到参数中,此时@within()会将参数入参名去找到注解的类型
30 * 凡是被Log标记的类,都会被拦截
31 *
32 * @param spriteLog 注解
33 */
34 @Before("@within(spriteLog)")
35 public void beforeAnnotationMatch(Log spriteLog) {
36 System.out.println("--------拦截被Log修饰类的所有方法" + spriteLog.tag() + "-------");
37 }
38
39
40 /**
41 * 返回值
42 *
43 * @param value 返回值
44 * @param spriteLog 注解
45 */
46 @AfterReturning(value = "@within(spriteLog)", returning = "value")
47 public void afterReturningAnnotationMatch(String value, Log spriteLog) {
48 System.out.println("afterReturningAnnotationMatch返回值:" + value + ",注解:" + spriteLog);
49 }
50
51 /**
52 * 拦截异常
53 *
54 * @param e 异常
55 * @param spriteLog 拦截日志
56 */
57 @AfterThrowing(value = "@within(spriteLog)", throwing = "e")
58 public void AfterThrowingAnnotationMatch(Exception e, Log spriteLog) {
59 System.out.println(e.getMessage());
60 }
61
62}3.3 this
a. API说明
拦截指定的类
b. 目录
text
1├── ThisMatchProcessor
2│ ├── AopThisMatchProcessor.java
3│ ├── ThisPerson.java
4│ └── readme.md
5c. 拦截代码
java
1@Aspect
2@Component
3public class AopThisMatchProcessor {
4
5 @Before(value = "this(ThisPerson)")
6 public void thisMatch() {
7 System.out.println("--------------ThisPerson------------");
8 }
9}
103.4 args
a. API说明
java
1@Component
2public class Person {
3
4 public String info(String name) {
5 return "姓名:" + name;
6 }
7
8 public String info(String name, Integer age) {
9 return "姓名:" + name + ",年龄:" + age;
10 }
11}Person类中有两个info方法,但是入参不一样,假如要拦截指定入参的方法时候,就可以使用args
b. 目录
text
1├── ArgsMatchProcessor
2│ ├── AopArgsMatchProcessor.java
3│ ├── Person.java
4│ └── readme.mdc. 拦截代码
可以看到args 和 within可以通过&&来进行,联合匹配。另外可以通过returning方法指定方法的返回值。但是注意,类型要和要拦截的方法的返回类型匹配。否则会报错。
java
1@Aspect
2@Component
3public class AopArgsMatchProcessor {
4
5 @AfterReturning(value = "within(Person) && args(name,age)", returning = "value")
6 public void beforeArgs(Integer age, String name, String value) {
7 System.out.println("拦截器逻辑----------------------------");
8 System.out.println("入参name:" + name);
9 System.out.println("入参age:" + age);
10 System.out.println("返回值:" + value);
11 System.out.println("拦截器逻辑----------------------------");
12 }
13}3.5 @annotation
a. API说明
拦截被指定注解标记的方法。
b. 目录
text
1├── AnnotationMethodMatchProcessor
2│ ├── AopAnnotationMethodMatchProcessor.java
3│ ├── LogMethod.java
4│ └── Main.java
5c. 代码
java
1@Aspect
2@Component
3public class AopAnnotationMethodMatchProcessor {
4
5
6 @Before(value = "@annotation(logMethod) && args(args)")
7 public void annotationMethodMatch(LogMethod logMethod, String args) {
8 System.out.println("注解方法匹配");
9 }
10}3.6 @args
a. API说明
拦截方法中入参被@args指定注解的方法。
b. 目录
text
1├── AnnotationArgsMatchProcessor
2│ ├── AopAnnotationArgsMatchProcessor.java
3│ ├── Apple.java
4│ ├── Fruit.java
5│ ├── Orange.java
6│ └── Teacher.javac. 代码
注意当出现以下异常说明aop声明的拦截范围太广泛了,导致了一些不能拦截的类被拦截从而报错了,此时只用缩小拦截的范围即可
text
1 Cannot subclass final class org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages缩小拦截范围如下使用this拦截指定类型
java
1@Aspect
2@Component
3public class AopAnnotationArgsMatchProcessor {
4
5 @Before(value = "@args(fruit) && this(Teacher)")
6 public void annotationMethodMatch(Fruit fruit) {
7 System.out.println("拦截被Fruit+tag:"+fruit.tag());
8 }
9}
103.7 execution
a. API说明
execution()是最常用的切点函数,其语法如下所示:
execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?) 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的
| 表达式 | 说明 |
|---|---|
| execution(public * *(..)) | 匹配所有目标类的public方法 |
| execution(* *Test(..)) | 匹配目标类所有以To为后缀的方法 |
| execution(spring.learning.Water.(..)) | 匹配Water接口所有方法 |
| execution(spring.learning.Water+.(..)) | 匹配Water接口以及实现类中所有方法(包括Water接口中没有的方法) |
| execution(* spring.learning.*(..)) | 匹配spring.learning包下所有的类所有方法 |
| execution(* spring.learning..*(..)) | 匹配spring.learning包及其子孙包下所有的类所有方法 |
| execution(* spring..*.Dao.find(..)) | 匹配包名前缀为spring的任何包下类名后缀为Dao的方法,方法名必须以find为前缀 |
| execution(* info(String,Integer)) | 匹配info方法中,第一个参数是String,第二个Integer的方法 |
| execution(* info(String,*))) | 匹配info方法中,第一个参数是String,第二个任意类型 |
| execution(* info(String,..))) | 匹配info方法中,第一个参数是String,后面任意参数 |
| execution(* info(Object+))) | 匹配info方法中,方法拥有一个入参,且入参是Object类型或该类的子类 |