AOP面向切面编程
AOP介绍
- AOP:Aspect Oriented Programming(面向切面编程),可以简单理解为面向特定方法编程
- 场景:
- 统计业务方法的执行耗时
- 记录系统的日志操作
- 事务管理(底层就是AOP,Spring对其封装成了@Transactional)
- 权限控制
- 优势:
- 较少重复代码
- 代码无侵入
- 提高开发效率
- 维护方便
AOP基础
- 需求:统计所有业务层方法的执行耗时
- 导入依赖:在pom.xml中引入AOP的依赖
1 | <dependency> |
- 编写AOP程序:针对特定的方法根据业务需要进行编程
1 |
|
@Aspect:标识该类为 Aspect(切面类)@Around:生效的范围(环绕通知注解),可以在目标方法执行前后进行拦截和处理execution:匹配方法执行连接点*:返回值类型(任意)com.norlcyan.service.impl.*:包路径下的所有类.*:所有方法(..):任意参数列表
pjp.proceed():可以看作是执行原始方法- 可以控制是否执行目标方法
- 可以修改方法参数或返回值
pjp.getSignature():获取方法的签名(方便定位具体哪个方法)
AOP核心概念
- 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
- 可以被控制不代表已经被控制了
- 切入点一定是连接点,连接点不一定是切入点
- 通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)
- 在基础案例中,recordTime就是通知方法
- 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
- 实际被控制的方法
- 简单理解为就是@Around注解中的execution参数指定的方法
- 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
- 目标对象:Target,通知所应用的对象
1 | 切面(Aspect) |

代理对象在Spring容器初始化时创建一次,而不是调用一次方法就创建一次代理对象
AOP进阶
通知类型
- @Around:环绕通知,此注解标注的通知方法在目标方法前、后都会被执行
- @Before:前置通知,此注解标注的通知方法在目标方法前被执行
- @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
- @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
- @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
注意:
- @Around环绕通知需要自己调用
ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行 - @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值
示例:
1 |
|
@PointCut:该注解的作用是将公共的切入点表达式抽取出来,许需要用到时引用该切入点表达式即可该注解修饰的方法,它的权限修饰符根据如下规则即可:
- private:仅能在当前切面类中引用该表达式
- public:在其他外部的切面类中也可以引用该表达式
通知顺序
- 当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行
- 执行顺序:
- 不同切面类中,默认按照切面类的类名字母排序:
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名考前的后执行
- 用
@Order(数字)加在切面类上来控制顺序- 目标方法前的通知方法:数字小的先执行
- 目标方法后的通知方法:数字小的后执行
- 不同切面类中,默认按照切面类的类名字母排序:
切入点表达式
- 介绍:描述切入点方法的一种表达式
- 作用:用来决定项目中的哪些方法需要加入通知
- 常见形式:
execution(...):根据方法的签名来匹配@annotation(...):根据注解匹配
execution
- execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配语法为:
1 | execution(访问修饰符? 返回值? 包名.类名.?方法名(方法参数) throws 异常?) |
- 其中带
?的表示可以省略的部分- 访问修饰符:可以省略(比如public、protected)
- 包名.类名:可以省略(不建议省略)
- throws异常:可以省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
*:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分- 比如:
execution(* com.*.service.*.update*(*))
- 比如:
..:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数- 比如:
execution(* com.norlcyan..DeptService.*(..))
- 比如:
示例:
1 |
|
另外,execution表达式还支持逻辑运算符:
1 |
|
上面表达式的意思是目标方法为list方法或delete方法
书写建议:
- 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:findXxx,updateXxx
- 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性
- 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名尽量不使用
..,使用*匹配单个包
@annotation
- @annotation切入点表达式,用于匹配标识有特定注解的方法
- 当execution表达式过于复杂,就可以考虑这种方式
1 |
|
1 | // 匹配到该注解 |
特定注解可以是自定义注解,也可以是其他依赖提供的注解
连接点
- 在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等
- 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint
- 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类
@Around:
1 |
|
其他通知:
1 |
|
案例
- 需求:将Tlias系统中的员工接口下的增、删、改操作以日志的形式记录到数据库表中
- 日志信息包括:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值,方法执行时长
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Norlcyan's Blog!