到底AOP是干啥的

20 阅读10分钟

事务管理

事务回顾

概念: 事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败。

操作:

开启事务(一组操作开始前,开启务):start transaction / begin
提交事务(这组操作全部成功后,提交事务):commit
回滚事务(中间任何一个操作出现异常,回滚事务):rollback

Spring事务管理

案例:解散部门:删除部门同时删除该部门下的员工

是可以正常删除的;

现在我们来模拟一个异常:

我们来删除教研部,(正常情况应该是教研部以及教研部的所有员工一起被删除)

可以看到教研部是删除成功了

但是教研部下的这些员工并没有删除,这就造成了数据不一致

原因就是在上面标红的那一行出现了异常,它下面的代码就不会再执行

解决办法就是让这个两个步骤要么一起成功要么一起失败,我们就需要让这两个操作处于同一个事务中

只要是进行事务控制都是这几个模式(开启、提交、回滚),但在spring中已经把这个事务控制的代码封装好了,它就是@Transactional这个注解

@Transactional

注解:@Transactional
位置:业务(service)层的方法上,类上,接口上
作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务

有了个注解之后,上面的操作异常之后,先删除的部门数据也会恢复回来。

事务进阶

rollbackFor

● 默认情况下,只有出现 RuntimeException (如之前的算术运算异常)才会回滚异常。比如这种异常就不会回滚

rollbackFor属性用于控制出现何种异常类型,回滚事务。 (下面这个是指所有异常)

propagation

● 事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

可以通过注解后面的属性propagation来指定事务传播行为。

我们来看看一些常见的事务传播行为:

来看一个案例:

·required:大部分情况下都是用该传播行为即可。
·requres_new:当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。


AOP基础

AOP: Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。

我们可以定义一个模板方法,把记录方法执行耗时这一部分 公共的逻辑代码抽取在这个模板方法当中

我们就可以在这个方法开始运行之前,来记录这个方法运行的开始时间,在方法结束运行的时候,来记录这个方法运行的结束时间,中间就来运行原始的业务方法(指的是 需要统计执行耗时的业务方法 一个或多个),而面向这样特定的一个或者多个方法进行编程,就叫做面向切面编程。

实现:
动态代理是面向切面编程最主流的实现。师springAop是spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。

它可以在不改动原始方法的基础上,针对于原始的方法进行编程,这个编程可以是功能的增强,也可以是功能的改变,面向切面编程是一种编程思想,动态代理技术是面向切面编程这种思想最主流的实现方式,SpringAOP它的底层就是基于动态代理技术,针对于特定方法进行编程。

下面是一个AOP的快速入门案例代码:

Aop核心概念

· 连接点:Join Point,可以被AOP控制的方法(暗含方法执行的相关信息)

比如上面这些方法都是可以被AOP控制的方法,所以这些所有的方法都是连接点

我们把这些方法的重复共性的步骤(在业务方法运行开始之前记录开始时间,在业务方法运行结束时记录结束时间)抽取出来就是通知

上图中表示所有方法都是切入点。

如果把切入点表达式换成下面list(),就表示仅仅只有list的这一个方法是切入点。


那我们现在知道了,通知就是定义要做什么;在什么时候做@Around代表的就是环绕,我们要在原始的方法运行之前、原始的方法运行之后来执行;这个切入点就定义了要在什么地方来应用 在DeptServiceImpl.list()方法上应用这个通知;这个通知与切入点结合在一起就形成了一个切面

通过这个切面,就能够描述当前AOP程序需要针对哪个原始方法,在什么时候执行什么样的操作,切面就是被@Aspect注解标识的类,一般被称为切面类。

以上就是AOP的核心概念。

AOP执行的流程

接下来我们再来分析一下,我们所定义的通知是如何与目标对象结合在一起,对目标对象当中的方法进行功能增强的。在前面讲到SpringAOP它的底层是基于动态代理技术来实现的;在程序运行时会自动的基于动态代理技术,为这个目标对象生成一个对应的代理对象,在这个代理对象当中,就会对目标对象当中的原始方法进行功能的增强,具体逻辑其实就是之前说的那一部分通知

AOP进阶

通知类型

  1. @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行(重点)

  2. @Before:前置通知,此注解标注的通知方法在目标方法前被执行

  3. @After: 后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行

  4. @AfterReturning: 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行

  5. @AfterThrowing: 异常后通知,此注解标注的通知方法发生异常后执行

@Slf4j
@Component
@Aspect
public class MyAspect1 {

    @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public void pt(){}

    @Before("pt()")
    public void before(){
        log.info("before ...");
    }

    @Around("pt()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before ...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();

        log.info("around after ...");
        return result;
    }

    @After("pt()")
    public void after(){
        log.info("after ...");
    }

    @AfterReturning("pt()")
    public void afterReturning(){
        log.info("afterReturning ...");
    }

    @AfterThrowing("pt()")
    public void afterThrowing(){
        log.info("afterThrowing ...");
    }
}

注意事项:

@Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行

@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。

这段是我们把每个通知前面的切入点表达式抽取出来需要用到时引用该切点表达式即可,这个被称为切点,以后有改动就只需要改这一个地方,用到了注解:@PointCut

通知顺序

当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。

那谁先运行,谁后运行呢?

切入点表达式

● 切入点表达式: 描述切入点方法的一种表达式

● 作用: 主要用来决定项目中的哪些方法需要加入通知

● 常见形式:

  1. execution(......): 根据方法的签名来匹配

  2. @annotation(......): 根据注解匹配

execution切入点表达式

execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)

● 其中带?的表示可以省略的部分

♦ 访问修饰符:可省略(比如: public、protected)

♦ 包名.类名: 可省略

♦ throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

· 可以使用通配符描述切入点

◆ * : 单个独立的任意符号,可以通配任意返回值,包名,类名,方法名,任意类型的一个参数,也可以通配包、类、方法名的一部分

execution(* com..service..update*()) ————哪哪都可以用

◆ .. : 多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数

execution(* com.itheima..DeptService.(..))

例子:

比如这个切入点表达式就是规定com.itheima.service.DeptService这个接口下的所有方法,但是要求这些方法的返回值是void、参数是Integer。

表示:返回值为任意、在com.itheima.service包下所有以Service结尾的类或接口中的以delete开头的并且有一个参数的方法。

表示:返回值为任意,在DeptService接口下的所有参数为任意个数任意类型的方法

表示:com开头、后面这一块是任意层级的包,然后要匹配的是DeptService这个接口或者类当中所有的方法,方法的形参是任意的;


完成一个小案例:

通过写切入点表达式来匹配list()和delete()这两个方法;

答案如下:

--- 书写建议 ---

● 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是 find 开头,更新类方法都是 update 开头。

● 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性。

● 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用…,使用*匹配单个包。

@annotation切入点表达式

当我们要匹配多个无规则的方法,比如上面同时要匹配list和delete这两个方法时,基于execution来就不是很方便了,我们是将两个切入点表达式组合在了一起,完成的这个需求,比较繁琐,这个时候我们可以借助于另一种切入点表达式 @annotation来简化

我们需要自己定义一个注解MyLog, 起到一个标识的作用

然后就可以在你要匹配的方法上(list和delete)加上@MyLog注解

再将切入点表达式改为:

就可以成功简化~

以上就是两种切入点表达式。

连接点

连接点可以简单理解为:可以被AOP控制的方法,(我们目标对象中所有的方法,都是可以被AOP控制的方法,在SpringAOP中这个连接点又特指方法的执行

● 在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint

对于其他四种通知,获取连接点信息只能使用 JoinPoint,它是 ProceedingJoinPoint 的父类型