我们总是听到AOP ,又称面向切面编程,那面向切面编程在日常开发中的应用场景有哪些呢 ?
我们来一起梳理一下:
什么时候会用到面向切面编程呢 ?其实就是有一些公共的逻辑,需要在很多地方用到,那这些代码如果在每个位置都写一下的话,当需要修改的时候,又必须将这些代码全都找出来进行修改,就会很冗余,为了解决这个问题:将公共的代码抽取出来,当代码运行到指定位置的时候,将公共的代码逻辑切入到相应的位置,即使用AOP的方式进行编程。
使用的是代理机制,在不改变原程序的基础上对代码进行增强操作,比如:统一的日志记录、性能监控、异常处理、参数校验 等等这些非业务核心的功能,被单独抽取出来,与业务代码分离,横切在核心业务代码之上AOP就是在某一个类或者方法执行的某个时机,声明在执行到这里之前、之后、中途要执行什么,执行完之后要接着执行什么。
我们其实可以利用自定义注解 和 切面来实现同样的效果。
一、要使用AOP切面编程的话,需要明确几个问题:
1、通过什么方式切入,是通过自定义注解,还是说直接指定某些包的位置。
2、在何时进行切入,这就涉及到我们考虑在所做业务的什么时机进行切入,执行相应的切面方法。
3、切入之后,需要做的公用操作是什么 ?日志记录、参数校验、权限校验、一异常处理等等。
二、专业术语
接下来,我们一起来明确一些切面编程上的专业术语:其中
连接点(Joinpoint)、切点(Pointcut)、通知(Advice)、切面(Aspect)为主要部分。
- 连接点Joinpoint
连接点描述的是位置
在程序的整个执行业务流程中,可以织入切面的位置。比如在某些待切方法的执行前后、参数调用处,异常抛出之后等位置。这些位置都可以理解成连接点。
- 切点Pointcut
切点本质上指方法,就是哪个方法将要被切入,那这个方法可以叫做切点(所以方法叫做切点,也就是执行过程中真正被植入切面的方法)
- 通知 Advice
通知其实就是指要实现增强效果的那些代码方法。后面就知道了,比如加了@Before方法的test()方法,那这个方法就是通知。
注意:
切面(切点+通知)就是在一个正常竖向做的业务流程中 ,在某些时机做横向拦截,然后做一些处理, 通过切点切入。链接点的链接,最终SpringAOP会将其定义的内容织入到约定的流程中,在动态代理中可以把它理解成一个拦截器。
三、常见的通知类型:
- 前置通知(before):在动态代理反射原有对象方法执行前 ,或者执行环绕通知前执行的通知功能。
- 后置通知(after):在动态代理反射原有对象方法执行后 , 或者执行环绕通知后执行的通知功能。无论是否抛出异常,他都会被执行。
- 返回通知(afterReturning):在原有对象方法正常返回后,或者执行环绕通知后正常返回(无异常)执行的通知功能。
- 异常通知(afterThrowing):在动原有对象方法异常返回后,或者执行环绕通知产生异常后执行的通知功能。
- 环绕通知(around):他可以转向当前被拦截对象的方法,并让之继续执行。
四、通知的执行顺序
spring4.x版本的通知的执行结果顺序是: https://blog.csdn.net/weixin_38174052/article/details/125281813
@Around注解方法的前半部分业务逻辑
->@Before注解方法的业务逻辑
->目标方法的业务逻辑
->@Around注解方法的后半部分业务逻辑(@Around注解方法内的业务逻辑若对ProceedingJoinPoint.proceed()方法没做捕获异常处理,直接向上抛出异常,则不会执行Around注解方法的后半部分业务逻辑;若做了异常捕获处理,则会执行)。
->@After(不管目标方法有无异常,都会执行@After注解方法的业务逻辑)这里是指被切入的方法的异常情况,而不是指切面方法是否发生异常的情况!!!这个的是亲自验证了一下。
->@AfterReturning(若目标方法无异常,执行@AfterReturning注解方法的业务逻辑)
->@AfterThrowing(若目标方法有异常,执行@AfterThrowing注解方法的业务逻辑)
spring源码版本是5.2.14
:所以包含通知注解的执行结果如下,
@Around注解方法的前半部分业务逻辑
->@Before注解方法的业务逻辑
->目标方法的业务逻辑
->@AfterThrowing(若目标方法有异常,执行@AfterThrowing注解方法的业务逻辑)
->@AfterReturning(若目标方法无异常,执行@AfterReturning注解方法的业务逻辑)
->@After(不管目标方法有无异常,都会执行@After注解方法的业务逻辑)
->@Around注解方法的后半部分业务逻辑(@Around注解方法内的业务逻辑若对ProceedingJoinPoint.proceed()方法没做捕获异常处理,直接向上抛出异常,则不会执行Around注解方法的后半部分业务逻辑;若做了异常捕获处理,则会执行)。
如果还是感觉不太清晰,这些顺序的代码验证请看: https://blog.csdn.net/monkey_wei/article/details/105521389
要被增强的类及方法:
正常执行结果如下:
抛异常结果如下:
到此通知的执行顺序搞清楚啦 。
五、接下来我们在看看切点表达式,的具体规则:
1、切点表达式
切点表达式就是用来定义 通知 (Advice) 往哪些方法上 切入的。
- 用于定义通知切入
- 而且不局限于某个方法,可以切入多个方法
- 告诉程序,这个切点可以匹配哪些方法
2、切点表达式的格式
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形参)) [异常])
- 访问权限修饰符:可选
默认是4个权限都包括
只写public就表示只包括公开的方法。
- 返回值类型:必填
星号* 表示返回值类型为任意
- 全限定名:可选
也就是类名,而且是具体的地址
两个点 .. 代表当前包及其子包下所有的类
省略时,表示所有的类
- 方法名:必填
星号* 表示所有方法
比如 set* 表示所有的set方法
-
形式参数列表: 必填
()
——表示没有参数的方法(..)
——参数类型和个数随意的方法(*)
—— 只有一个参数的方法(*,String)
—— 第一个参数随意,第二个参数是String的
-
异常:可选
- 省略时表示任意类型的异常
理解以下切点表达式 :
@Around(value = "execution(* com.xxx.xxxs.*.controller..*(..))") 这个就代表是返回值是任意类型,的 com.xxx.xxxs.*.controller包下的所有类及子包下的所有类,中的所有方法,而且是任意数量参数的方法都会被切入。
六、使用面向切面编程的可以解决的问题:
(1)前面提到了代理模式,可以极大的减少代码编写量,减少了代码耦合度,并且提高了可维护性
(2)对于复杂的业务,很多模块或者方法需要公用的逻辑:假设每个方法都需要日志/事务/权限,这些重复通用的业务和业务代码混杂在一起。交叉业务过多,就会导致以下两个问题
第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交又业务代码的话,需要修改多处。(维护不便)
第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。
结合老杜的横向编程的图,更好理解
面向切面编程的优点
- 将于业务逻辑无关的通用业务逻辑代码单独提取出来,减少了对业务代码的干扰
- 便于维护
- 减少了代码编写量(冗余
- 遵循了OCP开闭原则,并且实现了解耦,降低了代码的耦合度