本篇主要介绍Spring AOP的基础概念和入门使用
一、AOP的基本概念
AOP是一种面向切面编程的思想,它与IOC并称为Spring 的两大核心思想。什么是面向切面编程呢,具体来说就是对一类事情进行集中统一处理。这听起来像不像前面篇章中所介绍的统一功能处理?事实上,统一功能处理确实是对AOP的一种实现,但AOP在Spring中的实现并不仅仅体现于此,因为统一功能处理都是只能针对某种特定的场景,例如拦截器只能在接收请求时进行集中处理,而在Spring 对AOP的实现中,可以自定义要对哪类事情集中处理。
接下来我们来介绍一下AOP的一些相关概念
- 切面:整个集中处理的过程就叫切面
- 切点:提供一组规则,来判断要对哪些方法进行集中处理
- 连接点:满足切点规则的某一个方法,也就是要集中处理的所有方法中的具体的一个方法
- 通知:集中处理的具体方法逻辑
在Spring中,通知有多种类型:
- 环绕通知:包含了在连接点执行前与执行后所要执行的逻辑(在Spring中通过@Around注解标注)
- 前置通知:在连接点执行前所要执行的逻辑(在Spring中通过@Before注解标注)
- 后置通知:在连接点执行完后所要执行的逻辑(在Spring中通过@After注解标注)
- 返回后通知:在连接点返回数据后执行,如果连接点在返回数据前发生异常则不会执行(在Spring中通过@AfterReturning注解标注)
- 异常后通知:在连接点执行过程中发生异常后执行(在Spring中通过@AfterThrowing注解标注)
二、AOP的使用
单一切面
前面我们说过Spring AOP中可以自定义要对哪些类集中处理,因此,接下来我们通过对所有方法集中处理来具体体会一下Spring AOP的使用,集中处理的具体内容为记录所有方法的 执行时间。
首先我们得引入Spring aop的相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后我们得创建一个切面,具体为创建一个类,在类上加上一个@Aspect注解,用来标注当前类是一个切面类:
然后我们编写切面里包含的切点,通知等内容,由于我们要统计方法执行时间,因此我们可以使用环绕通知来统计也可以使用前置通知和后置通知搭配来统计,这里我们两种都写一下。具体代码如下(目标方法为我们的连接点):
这个切面代码还不能直接使用,因为我们还没有定义切点,我们可以直接将切点定义到通知注解里,具体如下:
关于这里对切点的具体的定义格式我们在下面在进行介绍,这里只要知道这个切点的规则是controller包下的所有方法就行 ,还有我们每次使用通知都得加上这样一个切点。
加好切点以后,一个切面类就定义好了,下面我们定义一个方法来测试一下:
借助浏览器访问这个接口后,控制台的打印如下:
可以发现通知和目标方法是按下图的顺序执行的
下面我们来了解一下异常后通知:
首先我们先在切面里定义一个异常通知:
然后在controller里新增一个test02方法,在这个方法里定义一个算术异常
然后我们通过浏览器测试一下这个接口,
控制台的输出如下:
通过日志可以发现,当目标方法发生异常时,环绕通知,和返回后通知都没有在执行了,返回后通知没有执行很好理解,因为目标方法还没有返回数据前发生异常了,所以目标方法就不会有返回值,也就不能触发返回后通知。为什么环绕通知也不执行了呢,我们来看一下执行目标方法的proceed方法的实现:
可以发现它把异常上抛给环绕通知了,所以环绕通知直接结束了。如果我们不想让环绕通知因异常而结束,可以在目标方法里直接将异常catch住,这样异常就不会上抛给环绕通知了,环绕通知也就不会发生影响了,代码如下
我们也可以在环绕通知里对异常进行catch:
打印的日志如下:
可以发现环绕通知都完整执行了。
多个切面
这里我们介绍的是只有一个切面的情况,下面我们再来看一下多个切面的情况,这里我们在定义切面类CostTimeAdvice1,具体内容如下:
我们在来访问一下test01
日志如下:
通过日志可以发现,当存在多个切面时是按一种类似一种同心圆的顺序执行的,具体如下:
通过图片可以发现目标方法外面被包裹了两层切面。
这两层切面的包裹顺序是可以自定义的。具体为通过给切面设置优先级,设置优先级就得使用@Order注解,这个注解可以给类设置优先级,当进行使用时优先使用优先级高的类。在@Order注解里需要设置一个数值,这个数值越小,类的优先级越高,反之。数越大,类的优先级越低。接下来我们来通过设置优先级来调整一下这两个切面的顺序:
然后我们来测试一下:
通过日志可以发现,切面的顺序果然被调整了。
通过上述对Spring AOP的使用我们可以总结出使用AOP有如下优点:
- 在不侵入代码(也就是不修改原始代码)的前提下,对代码的功能进行增强(就是对目标方法进行拓展,例如我们的目标方法原本是不能记录执行时长的,通过切面就有记录时长的功能了)
- 减少了重复的代码
- 便于维护
- 提高开发效率
三、切点表达式
@pointcut
前面我们在定义切点时都得写一大长串,这样未免太麻烦,因此我们可以通过使用@pointcut注解来简化对切点的定义。
具体如下:
先写一个@pointcut注解,然后在注解里编写切点规则,然后再注解下定义一个方法,具体如下:
这样一个切点就设置好了。
如果我们要使用这个切点,直接在通知注解里引入这个方法即可:
excution表达式
excution表达式是一种常用的切点表达式,他用来表示一组规则,其语法如下:
excution(<访问限定符><返回类型> <包名.类名.方法名(方法参数)><异常>)
通常情况下,访问限定符和异常可以被省略。前面我们使用的excution就进行了省略。excution支持使用正则表达式,正则表达式的语法如下:
- ' * ' 表示任意一个字符串或者一个字符 ,因此可以用来表示包名,数据类型,类名,方法名等
- ' . '表示任意一个字符,这个字符可以是一个普通字符,也可以是一个' * '
- ’ ..'表示任意数量个 ‘ . '
@annotation表达式
使用excution表达式来定义切点其实存在一定缺陷,因为它是一组规则,只有符合规则的方法才能处理,但有时候我们所需要集中处理的方法并没有一个统一的规则,这时我们就需要使用@Annotation表达式了。使用这个注解的流程如下:
- 创建自定义注解
- 在需要集中处理的方法上加上该自定义注解
- 在通知上使用@annotation表达式
接下来我们来具体演示一下,首先我们创建一个@MyAspect注解(在new时选择@anonotation):
(大括号里还可以为注解设置一些属性,这里暂且不想去,就不设置了)
然后我们在我们的test01方法上加一下这个注解
然后我们将切面所有通知注解都改为使用@annotation表达式:
然后我们通过浏览器来测试一下:
通过日志可以发现使用@annotation也成功设置好切点了。
Spring实现Aop的方式
最后我们在来看一个面试题:
Spring实现AOP的方式有哪些?
- 基于@Aspect注解和excution表达式
- 基于@Aspect和@annotation表达式
- 基于Spring AOP的相关api(配置xml)
- 基于代理
其中前面两种方式在上面的文章中已经演示过了,至于后面这两种方式这里就不介绍了,因为这两种方式已经逐渐过时,很少有人使用了。