Spring AOP
(Aspect-Oriented Programming)切面式编程。如何理解切面呢,最开始听到这个词我也觉得非常抽象,后来在刷blibli的时候总能看到一些精彩片段,这些片段就是从完整的直播或者比赛视频中剪切的,他们把这种视频叫做切片,我突然就悟了!
给这些精彩片段就是我们需要额外处理的代码,视频片段的前面和后面就是我们说的“切面”
Spring中的切面编程,通常就是对方法的增强,在方法调用前后进行额外的操作。Spring AOP 只支持 Spring Bean 的方法切入,所以切点表达式只会匹配 Bean 类中的方法。 Spring AOP实现了 JDK动态代理,以使用目标类和Advice调用创建代理类,这些称为AOP代理类。
快速入门
我们先在SpringBoot中切一个方法吧,这里使用注解的方式切入
- 在pom.xml中导入AOP依赖
1 | <dependency> |
- 创建注解类
1 |
|
- 创建切面类,在切面类中写一个方法,加上@Around注解,在注解中写上新建的注解类路径
1 |
|
- 在切点初编写额外操作
1 |
|
- 在方法上添加@TestAnnotation注解
1 |
|
- 调用该方法,查看结果
1 | 方法开始 |
切面组件Aspect
spring中设置切面通常有两种方式
- xml配置文件中声明bean,再在aop:config的aop:aspect中指定为切面
- 给类加
@Component
和@Aspect
注解,快速入门就是使用这种方法设置
AOP 增强的类型
AOP 增强的方式有五种类型,分别是方法调用前、方法调用后、方法调用前后、抛出异常时、执行结束时,快速入门中使用的是Around
,在方法执行前后都进行了打印
- Before Advice:在连接点之前执行的方法增强
@Before
- After Advice:在连接点之后执行的方法增强
@AfterReturning
- Around Advice:在连接点前后执行的增强,Around环绕着连接点
@Around
- After Throwing:在连接点抛出异常时执行的方法增强
@AfterThrowing
- After Returning:当方法执行成功时的增强
@After
切点表达式配置
回忆一下我们对方法进行增强的主要步骤
- 找到需要增强的地方(这个地方也叫切点)
- 编写具体增强的内容
在快速入门中,我们用这一个方法就完成了这两步
1 |
|
还有一种方式是通过两个方法:先定义切点、再定义增强内容,这样切点和增强方法就会解耦,可以提升代码的可维护性和可重复使用性,如下所示
1 |
|
再次调用,可以看到输出结果是一样的
1 | 方法开始 |
切点表达式
现在来介绍一下切点表达式,这个表达式的作用就是找到具体要切入的点,通常有这几种切入方式
使用
execution
表达式:
使用execution
表达式是定义切点的最常见方式之一。通过execution
表达式可以匹配方法的执行。例如:1
2
3
4
5
6
7
8
9
10
11// 匹配public方法
execution(public * *(..))
// 匹配名称以set开头的方法
execution(* set*(..))
// 匹配AccountService接口或类的方法
execution(* com.xyz.service.AccountService.*(..))
// 匹配service包及其子包的类或接口
execution(* com.xyz.service..*(..))上述代码定义了一个切点
serviceMethods()
,它匹配com.example.service
包中所有类的所有方法。使用
within
表达式:
使用within
表达式可以定义基于类型的切点,匹配指定包或类型的所有方法。例如:1
2
3
4
5
6
7
8// 匹配service包的类
within(com.xyz.service.*)
// 匹配service包及其子包的类
within(com.xyz.service..*)
// 匹配AccountServiceImpl类
within(com.xyz.service.AccountServiceImpl)上述代码定义了一个切点
inServicePackage()
,它匹配com.example.service
包中所有类的所有方法。使用
this
和target
关键字:
使用this
和target
关键字可以定义基于目标对象类型的切点,匹配特定类型或实现了特定接口的目标对象。例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 匹配代理对象类型为service包下的类
this(com.xyz.service.*)
// 匹配代理对象类型为service包及其子包下的类
this(com.xyz.service..*)
// 匹配代理对象类型为AccountServiceImpl的类
this(com.xyz.service.AccountServiceImpl)
// 匹配目标对象类型为service包下的类
target(com.xyz.service.*)
// 匹配目标对象类型为service包及其子包下的类
target(com.xyz.service..*)
// 匹配目标对象类型为AccountServiceImpl的类
target(com.xyz.service.AccountServiceImpl)上述代码分别定义了基于当前代理对象和目标对象类型的切点。
使用自定义注解:
可以定义自定义注解,并在切点表达式中使用该注解来标注需要被切入的方法。例如:1
快速入门中就是使用自定义注解的方式定义切点。
使用参数:
@args
1
2
3
4
5// 匹配参数只有一个且参数类使用了Demo注解
// 匹配参数个数至少有一个且为第一个参数类使用了Demo注解组合多个切点
可以通过逻辑运算符(如&&
、||
、!
)将多个切点组合起来,形成复合切点。例如:1
上述代码定义了一个切点
publicServiceMethods()
,它匹配com.example.service
包中所有公共方法,但排除com.example.service.internal
包中的方法。
Execution表达式说明
表达式模式:
1 | execution(modifier? ret-type declaring-type?name-pattern(param-pattern) throws-pattern?) |
- modifier:匹配修饰符,
public
,private
等,省略时匹配任意修饰符 - ret-type:匹配返回类型,使用
*
匹配任意类型 - declaring-type:匹配目标类,省略时匹配任意类型
..
匹配包及其子包的所有类
- name-pattern:匹配方法名称
*
匹配任意方法set*
匹配以set
开头的方法
- param-pattern:匹配参数类型和数量
()
匹配没有参数的方法(..)
匹配有任意数量参数的方法(*)
匹配有一个任意类型参数的方法(*,String)
匹配有两个参数的方法,并且第一个为任意类型,第二个为String
类型的
- throws-pattern:匹配抛出异常类型,省略时匹配任意类型
AOP术语
- Aspect: 方面是一个模块,其中封装了advice和pointcuts,并提供cross-cutting可以有许多方面。我们可以使用带有 @Aspect 批注的常规类来实现方面。
- Pointcut: 切入点是一种表达式,它选择一个或多个执行Advice的连接点。我们可以使用expressions或patterns定义切入点。它使用与联接点匹配的不同类型的表达式。在Spring Framework中,使用 AspectJ 切入点表达语言。
- Join point: 连接点是应用程序中应用 AOP方面的点。或者它是Advice的特定执行实例。在AOP中,连接点可以是方法执行,异常处理,更改对象变量值等。
- Advice: Advice是我们在方法执行之前before或after采取的措施。该动作是在程序执行期间调用的一段代码。SpringAOP框架中有五种类型的Advice: 在Advicebefore, after, after-returning, after-throwing和around advice。 是针对特定join point的Advice。
- Target object: 一个应用了Advice的对象称为target object。目标对象始终是proxied,这意味着在运行时将创建一个覆盖目标方法的子类,并根据其配置包含Advice。
- Weaving: 这是将各个方面与其他应用程序类型进行linking aspects的过程。我们可以在运行时,加载时间和编译时进行织造。