【Spring】深入解析 Spring AOP 核心概念:切点、连接点、通知、切面、通知类型和使用 @PointCut 定义切点的方法
Spring AOP
下面我们再来详细学习 AOP,主要是以下几部分:
Spring AOP 核心概念
切点(Pointcut)
切点(Pointcut),也称之为“切入点”。
Pointcut
的作用就是提供一组规则(使用 AspectJ pointcut expression language
来描述),告诉程序对哪些方法来进行功能增强。
上面的表达式 execution(* com.example.demo.controller.*.*(..))
就是切点表达式。
连接点(Join Point)
满足切点表达式规则
的方法
,就是连接点
。也就是可以被 AOP 控制
的方法。
以入门程序举例,所有 com.example.demo.controller 路径下的方法,都是连接点。
上述 BookController 中的方法都是连接点。
切点和连接点的关系:
连接点
是满足切点表达式
的元素
。切点
可以看做是保存了众多连接点
的一个集合
。 比如:切点表达式
:全体偶像练习生连接点就是
:蔡徐坤、范丞丞等各个偶像练习生
通知(Advice)
通知
就是具体要做的工作
,指哪些重复的逻辑
,也就是共性功能
(最终体现为一个方法
)。
比如上述程序中记录业务方法的耗时时间,就是通知
。
在 AOP 面向切面编程当中,我们把这部分重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容。
切面(Aspect)
切面(Aspect)= 切点(Pointcut)+ 通知(Advice)。
通过切面就能够描述当前 AOP 程序需要针对于哪些方法,在什么时候执行什么样的操作。
切面既包含了通知逻辑的定义,也包括了连接点的定义。
切面所在的类
,我们一般称为切面类
(被 @Aspect 注解标识的类
)。
通知类型
上面我们讲了什么是通知,接下来学习通知的类型
。@Around
就是其中一种通知类型
,表示环绕通知
:
Spring 中 AOP
的通知类型
有以下几种:
注解名称 | 描述 |
---|---|
@Around | 环绕通知,此注解标注的通知方法在目标方法前、后都被执行。 |
@Before | 前置通知,此注解标注的通知方法在目标方法前被执行。 |
@After | 后置通知,此注解标注的通知方法在目标方法后被执行的,无论是否有异常都会执行。 |
@AfterReturning | 返回后通知,此注解标注的通知方法在目标方法后被执行的,有异常不会执行。 |
@AfterThrowing | 异常后通知,此注解标注的通知方法在发生异常后执行。 |
接下来我们通过代码来加深对这几个通知的理解:
为方便学习,我们可以新建一个项目,使用 web+lombok 依赖,删除 pom.xml 文件多余部分:
添加 AOP 依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>
先创建测试类接口:
添加连接点:
创建切面(需引入 AOP 依赖):
@Slf4j
@Aspect
@Component
public class AspectDemo1 {/*** @Around(环绕通知):此注解标注的通知方法在目标执行前,后都被执行*/@Around("execution(* com.bit.springaopdemo.Controller.*.*(..))")public Object TimeAspect(ProceedingJoinPoint pjp) throws Throwable {log.info("目标方法执行前.......");Object result = pjp.proceed();log.info("目标方法执行后.......");return result;}/*** @Before(前置通知),此注解标注的通知方法在目标方法前被执行*/@Before("execution(* com.bit.springaopdemo.Controller.*.*(..))")public void doBefore(){log.info("doBefore.......");}/*** @After(后置通知),此注解标注的通知方法在目标方法后被执行的,无论是否有异常都会执行。*/@After("execution(* com.bit.springaopdemo.Controller.*.*(..))")public void doAfter(){log.info("doAfter.......");}/*** @AfterReturning(返回后通知),此注解标注的通知方法在目标方法后被执行的,有异常不会执行。*/@AfterReturning("execution(* com.bit.springaopdemo.Controller.*.*(..))")public void doAfterReturning(){log.info("AfterReturning.......");}/*** @AfterThrowing(异常后通知),此注解标注的通知方法在发生异常后执行。*/@AfterThrowing("execution(* com.bit.springaopdemo.Controller.*.*(..))")public void doAfterThrowing(){log.info("AfterThrowing.......");}
}
运行程序,观察日志:
1、正常运行的情况:
http://127.0.0.1:8080/test/t1
观察日志:
这个日志中,我们还是不知道目标方法 t1() 是什么时候执行的,再打上一个日志,重新运行程序,并发送请求:
程序正常运行的情况下,@AfterThrowing
标识的通知方法
不会执行。
从上图也可以看出来,@Around
标识的通知方法
包含两部分,一个“前置逻辑”
,一个“后置逻辑”
。
其中“前置逻辑”
会先于 @Before
标识的通知方法
执行,“后置逻辑”
会晚于 @After
标识的通知方法执行:
2、异常时的情况:
为了方便观察,我们给 Controller 中的每个接口都加上 log 日志:
测试方法 t1() 是没有异常的情况,接下来,我们来测试目标方法存在异常的情况:
观察日志:
程序发生异常
的情况下:
-
@AfterReturning
标识的通知方法不会执行; -
@AfterThrowing
标识的通知方法执行了;
如果我们在环绕通知 @Around
中,使用 try....catch .....
对连接点(目标方法)
的异常进行捕获,能否达到 @AfterThrowing
的功能呢?
@Around 环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会再执行了(因为原始方法调用出异常了)。
注意事项:
-
@Around 环绕通知
需要调用ProceedingJoinPoint.proceed()
来让原始方法执行
,其他通知不需要考虑目标方法执行
。 -
@Around 环绕通知
的方法返回值,必须指定为 Object
,来接收原始方法的返回值
,否则原始方法执行完毕,是获取不到返回值的。 -
一个切面类
可以有多个切点
。
定义切点@PointCut
上面代码存在一个问题,就是存在大量重复的切点表达式:
execution(* com.example.demo.controller.*.*(..))
。
Spring 提供了 @PointCut
注解,把公共的切点表达式
提取出来,需要用到时引用该切入点表达式
即可。
上述代码就可以修改为:
当切点定义使用 private
修饰时,仅能在当前切面类中
使用。
当其他切面类
也要使用当前切点定义
时,就需要把 private
改为 public
。
引用方式为:全限定类名.方法名()
: