SpringBoot2 | 第二十五篇(二):SpringAOP使用详情

​ 从上一篇的案例中我们可以看到 Spring AOP 已经初露锋芒了。接下来我们将详细介绍 Spring AOP 的详细使用。Spring AOP 使用方式有两种:XML 配置 和 注解方式。本篇笔记都是基于注解形式的。

名称介绍

  • 切面(Aspect):通知 + 切点 = 切面

  • 切点(Pointcut):如果通知定义了“什么”和“何时”。那么切点就定义了“何处”。切点会匹配通知所要织入的一个或者多个连接点。

  • 通知(Advice):通知定义了切面是什么以及何时使用。除了秒速切面要完成的工作,通知还解决了何时执行这个工作的问题。通知分为 前置通知、后置通知、返回通知、异常通知、环绕通知

  • 连接点(JoinPoint):连接点是一个应用执行过程中能够插入一个切面的点。就是spring允许你是通知(Advice)的地方,那可就真多了,基本每个方法的前、后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那不是咱们关注的,只要记住,和方法有关的前前后后都是连接点。

  • 引入(Introduction):引入允许我们向现有的类添加新方法或属性(以后有空再研究)

  • 目标(Target):引入中所提到的目标类,也就是要被通知的对象

  • 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有3个点可以进行织入

    1. 编译期——切面在目标类编译时期被织入,这种方式需要特殊的编译器。AspectJ 的织入编译器就是以这种方式织入切面。

    2. 类加载期——切面在目标类加载到JVM时被织入 ,这种方式需要特殊的类加载器,他可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5 的 加载时织入(load-time weaving,LTW) 就支持这种织入方式

    3. 运行期——切面在应用运行期间的某个时刻被织入。一般情况下,在织入切面的时候,AOP 容器会为目标对象动态的创建代理对象。Spring AOP 就是以这种方式织入切面。

AspectJ 注解

切面类

  • @Aspect:标注该类为切面类

切点

  • @Ponitcut:标注的方法为切点

通知

  • @Before:在目标方法被调用之前调用通知功能

  • @After:在目标方法完成之后调用通知,无论方法执行成功与否

  • @AfterReturning:在目标方法成功执行之后调用

  • @AfterThrowing:在目标方法抛出异常时调用通知

  • @Around:环绕通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

Pointcut expression

切点表达式分为三部分:designators、wildcards、operators。

designators(指示器)

1541831050337

1. 匹配方法

  • execution()重点掌握

    • @Pointcut("execution(public * com.fatal.service.*Service.*(..))")

      匹配任意返回值,service包下的以Service结尾的所有方法

    • @Pointcut("execution(public * com.fatal.service..*Service.*(..))")

      匹配任意返回值,service包及其子包下的以Service结尾的所有方法

    • @Pointcut("execution(public String com.fatal.service..*Service.*(..))")

      匹配返回String类型,service包及其子包下的以Service结尾的所有方法

    • @Pointcut("execution(public * com.fatal.service..*Service.*(..) throws *)")

      匹配任意返回值,service包及其子包下的以Service结尾,可能抛出任意异常的所有方法(只有方法上面标注了 throws … 才会被匹配到)

      1541846683426

2. 匹配注解

  • @annotation()(关注点为方法级别

    • @Pointcut(“@annotation(com.fatal.anno.AdminOnly)”)

      匹配所有标注有@AdminOnly注解的方法

  • @within()(关注点为类级别

    • @Pointcut(“@within(com.fatal.anno.NeedSecured)”)

      匹配所有标注有@NeedSecured的类的所有方法,要求 annotation 的 RetentionPolity 为 CLASS

  • @target()(关注点为类级别)与 within() 一起用

    • @Pointcut(“@target(com.fatal.anno.NeedSecured)”)

      匹配所有标注有@NeedSecured的类的所有方法,

      要求: annotation 的 RetentionPolity 为 RUNTIME ;必须与 within() 一起用

  • @args()(关注点为参数级别)与 within() 一起用

    • @Pointcut(“@args(com.fatal.anno.NeedSecured)”)

      匹配传入的参数类标注有@NeedSecured的方法;要求必须与 within() 一起用

    1541844605143

@within() 和 @target() 的区别

在 Spring 环境下的话,也就是我们演示的环境,它们没有任何区别;当不在 Spring 环境下的时候,它们的区别如下:

  • 要求 annotationRetentPolicyclass 级别
  • 要求 annotationRetentPolicyruntime 级别
补充

​ 这里补充一下,使用 @target()@args() 必须指定需要扫描的类的范围,不然会报错,如视频中,去掉 within 限制 @args()@target() 都会报错。

3. 匹配包/类

  • within()

    • @Pointcut(“within(com.fatal.service.ProductService)”)

      匹配 指定类 的所有方法

    • @Pointcut(“within(com.fatal.service..*)”)

      匹配 指定包及子包下所有类 的所有方法

    1541834166211

4. 匹配对象

  • this()

    • @Pointcut(“this(com.fatal.log.Loggable)”)

      匹配 指定接口 生成的组件的代理对象的方法

  • target()

    • @Pointcut(“target(com.fatal.log.Loggable)”)

      匹配 指定接口 生成的组件的被代理对象的方法

  • bean()

    • @Pointcut(“bean(beanId)”)

      匹配组件名为beanId的bean里边的方法

      注意:括号里边为bean的id。默认为类名首字母小写

    • @Pointcut(“bean(*Service)”)

      匹配所有 以Service结尾 的bean里头的方法

    1541837636001

this() 与 target() 的区别

这里和引入有关了

  • this: 可以拦截 DeclareParents(Introduction)
  • target: 不拦截 DeclareParents(Introduction)

5. 匹配参数

  • args()

    • @Pointcut(“args(Long) && within(com.fatal.service.*)”)

      匹配 指定类 中任何 以find开头而且只有一个Long参数 的方法

    • @Pointcut(“args(Long,..) && within(com.fatal.service.*)”)

      匹配 指定类 中任何 以find开头的而且第一个参数为Long型 的方法

1541838819172

wildcards(通配符)

  • *:匹配任意数量的字符
  • +:匹配指定类及其子类
  • ..:一般用于匹配任意数的子包或参数

operators(运算符)

  • && (与)
  • ||(或)
  • !(非)

Advice

  • @Before:前置通知

  • @After:后置通知,方法执行之后(相当于finally

    1541944108097

    测试结果:相当于finally。方法执行之后不管是否(抛异常方法就不能返回)正常返回都会走切面逻辑

  • @AfterReturning:返回通知,成功执行之后

    1541944147986

    测试结果:不管是否有返回值,只要方法正常执行完成就会走切面逻辑

  • @AfterThrowing:异常通知,抛出异常之后(只要抛异常,就会走切面逻辑)

    1541944210328

    测试结果:只要方法一抛出异常,就会走切面逻辑

  • @Around:环绕通知(相当于其他注解的总和)

    1543482575152

测试结果:功能相当于前置通知后置通知返回通知环绕通知的结合

笔记

  1. 返回通知怎么获取目标方法返回值
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 返回通知
*/
@AfterReturning(returning = "result", value = "point()")
public Object filter(Object result) {
try {
System.out.println(result);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
  1. execution 只能修饰公共方法,public 可以省略

参考资料

探秘Spring AOP

总结

本篇笔记记录的内容:

  • Pointcut expression 的组成部分
  • 各种 designators 的区别
  • 5种 advice 及参数/结果绑定

SpringBoot 自动开启 AOP 功能了,我们可以直接使用 @Transactional 、@Async 或者自定义的基于AOP 的注解实现切面编程

源码有点多,就不贴出来了。需要的话去 GitHub 上看。可以检出来跑跑看。。。

SpringBoot的知识已经有前辈在我们之前探索了。比较喜欢的博主有:唐亚峰 | Battcn方志朋的专栏程序猿DD纯洁的微笑。对这门技术感兴趣的可以去他们的博客逛逛。谢谢他们的分享~~

以上文章是我用来学习的Demo,都是基于 SpringBoot2.x 版本。

源码地址: https://github.com/ynfatal/springboot2-learning/tree/master/chapter25_2

学习 apollo_0001 前辈的经验