Spring技术AOP基础
AOP基础
一、简介
- AOP:Aspect Oriented Programming(面向切面编程,面向方面编程),其实就是面向特定的方法编程。
- 动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。
- 使用场景:记录操作日志、权限控制、事务管理…
- 优势:代码无侵入、减少重复代码、提高开发效率、维护方便
二、SpringAOP快速入门
问题:统计各个业务层方法的执行耗时
导入依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>编写AOP程序:针对特定的方法根据业务需要进行编程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//日志打印
//交给IOC管理
//告知Spring这是一个AOP
public class TimeAspect {
//说明该切面作用在那些方法上
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
//1. 记录开始时间
long begin = System.currentTimeMillis();
//2. 调用原始方法执行
Object result = joinPoint.proceed(); //result是原始方法执行之后的返回值
//3. 记录结束时间,计算方法执行耗时
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature()+"执行耗时:{}ms",end - begin);
return result;
}
}此时,访问各个接口可以看到结果,说明实现成功…
三、核心概念
- 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
- 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
- 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
- 切面:Aspect,描述通知与切入点的对应关系(通知 + 切入点)
- 目标对象:Target,通知所应用的对象
四、通知类型
Around
:环绕通知,此注解标注的通知方法在目标方法前、后被执行Before
:前置通知,此注解标注的通知方法在目标方法前被执行After
:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会被执行AfterReturning
:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行@AfterThrowing
:异常后通知,次注解标注的通知方法发生异常后执行
注意事项
@Aroudn
环绕通知需要自己调用 **ProceedingJoinPoint.proceed( )**来让原始方法执行,其他通知不需要考虑目标方法执行。@Around
环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。
测试代码展示
1 | //日志打印 |
运行之后,查看结果。
回过头来看看我们编辑的代码,每一个切入点都包含一个重复的切入点表达式,这样会显得代码繁琐,因此可以使用注解@Pointcut
简化
1 | //日志打印 |
不同切面类之间定义的切入点是可以共享的
首先在
TimeAspect
类中定义一个切入点在
MyAspect
类中使用TimeAspect
的切入点启动项目查看运行结果:MyAspect的切入成功实现即可说明切入点可以共享
五、通知顺序
- 场景:当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行
这里我定义了三个切面类并且实现了对应的@Before
和@After
运行程序之后,发送一个请求,查看切面的运行结果为下图所示:
发现规律:与切面类的类名相关
- 不同切面类中,默认按照切面类的类名字母排序
- 在原始方法运行之前的通知:类名排名越靠前越先执行
- 在原始方法运行之后的通知:类名排名越靠前越后执行
- 使用
@Order(数字)
加在切面类上来控制顺序- 在原始方法运行之前的通知:数字小的越先执行
- 在原始方法运行之后的通知:数字小的越后执行
例子:使用@Order来指定先让Aop_2的前置通知先执行,然后是Aop_3最后执行Aop_1
- 首先在对应的Aop类上加上注解@Order注解因为先执行Aop_2所以它的 数字最小
- 启动程序,发送请求,查看结果。结果与注解@Order相对应
六、切入点表达式
切入点表达式:描述切入点方法的一种表达式
作用:主要用来决定项目中的哪些方法需要加入通知
常见形式:
execution(...)
:根据方法的签名来匹配@annotation(...)
:根据注解匹配
第一类:execution
execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
1 | execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?) |
- 其中带 ? 表示可以省略的部分
- 访问修饰符:可省略(比如:public、protected)
- 包名.类名:可省略
- throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
1 |
|
可以使用通配符描述切入点
*
:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类方法名的一部分1
execution(* com.*.service.*.update*(*))
..
:多个连续的任意符号,可以通配任意层级的包、或任意类型、任意个数的参数1
execution(* com.itheima..DeptService.*(..))
注意事项:
- 根据业务需要,可以使用 且(&&)、或(||)、非(!)来组合比较复杂的切入点表达式
书写建议:
- 所有业务方法名在命名时尽量规范,方便切入点表达式的快速匹配。如:查询类方法都是find开头,更新类方法都是update开头
- 描述切入点方法通常是基于接口描述,而不是直接描述实现类,增强拓展性
- 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用
..
,使用*
匹配单个包
第二类:@annotation【基于注解开发】
首先定义一个注解
- **
@Retention
**:该注解用来说明自定义注解在什么时候生效 - **
@Target
**:用于描述注解在哪里生效
然后,修改MyAspect
的内容将注解@Around
中的内容更换成@annotation("自定义注解所在的包")
在需要使用到MyAspect切面的地方添加上自定义注解@MyAnnotation
【此处我在Login上添加注解】然后启动项目,测试结果
可以看到在执行Login
的时候切面类MyAspect
确实也执行了说明基于自定义注解@annotation
成功实现
七、连接点
- 在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等
- 对于
@Around
通知,获取连接点信息只能使用ProceedingJoinPoint
- 对于其它四种通知,获取连接点信息只能使用
JoinPoint
,它是ProceedingJoinPoint
的父类型
- 对于
1 |
|
八、操作例子
下面给出一个具体业务实际的操作例子来巩固AOP操作,具体场景如下:在实际开发中常常会遇到对数据库的修改以及添加操作,在某些时刻每一次添加以及修改的操作的时候需要记录下操作人以及操作时间,因此在很多方法上会有冗余的代码【即:记录操作人以及修改时间】,此类问题称为公共字段填充。
实现思路
序号 | 字段名 | 含义 | 数据类型 | 操作类型 |
---|---|---|---|---|
1 | create_time | 创建时间 | datetime | insert |
2 | create_user | 创建人id | bigint | insert |
3 | update_time | 修改时间 | datetime | insert、update |
4 | update_user | 修改人id | bigint | insert、update |
- 自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法
- 自定义切面类AutoFillAspect,统一拦截加入AutoFill注解的方法,通过反射为公共字段赋值
- 在Mapper的方法上加入AutoFill注解
技术点:枚举、注解、AOP、反射
在清楚操作步骤之后,现在开始实现上述的过程
- 首先自定义注解
1 | /** |
- 自定义切面类之前,首先创建一个constant保存字符串【利于后期统一修改用到字符串的地方】
1 | public class AutoFillConstant { |
- 然后就可以开始定义切面类
1 | /** |
- 随后在需要进行公共字段填充的地方添加上自定义的注解即可
- 标题: Spring技术AOP基础
- 作者: 忘记中二的少年
- 创建于 : 2023-10-03 12:00:00
- 更新于 : 2023-10-06 09:43:00
- 链接: https://github.com/HandsomeXianc/HandsomeXianc.github.io/2023/10/03/AOP基础/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。