Spring AOP



 

理解概念:

在 Spring AOP 中,有这几种概念通常令人晦涩难懂,但又必须理解,下面将用通俗易懂的方式介绍它们:

  1. 切面(Aspect):
            可以将切面理解为一个横切关注点的模块化单元。它是一个包含了一组横切关注点的,它定义了在哪里和何时应该执行这些横切关注点。例如,日志记录、事务管理等都可以作为一个切面。

  2. 切点(Pointcut):
             切点就是在应用程序中定义的一个或多个特定的方法它们被用来确定切面在何处使用。切点可以通过表达式或者正则表达式等方式来定义。例如,当方法名以"save"开头时,就可以作为一个切点。(后面介绍)

  3. 连接点(Joinpoint):
              连接点是在切面中可能触发切点的点。它代表了在程序执行过程中,切面可以插入的位置。例如,方法的调用、方法的执行、异常的抛出等都可以作为连接点。

  4. 通知(Advice):
              通知是切面在特定连接点上执行的动作。它定义了在切点处执行的代码逻辑通知可以在连接点之前、之后或者环绕连接点执行。例如,前置通知在方法执行之前执行,后置通知在方法执行之后执行。

        可以通过一个简单的例子来说明这些概念:假设有一个日志切面,它在程序中的所有方法执行之前和之后记录日志。那么,切面就是这个日志切面,它定义了在何处和何时记录日志。切点就是指定了哪些方法需要被记录日志,例如所有的Service层方法。连接点就是这些方法的调用或执行的位置。通知就是在连接点之前或之后执行的记录日志的动作。


实现 AOP 逻辑:

        下面将实现一个AOP的逻辑,主要功能为:当访问登录、注册等功能时,某些方法属于切点方法,有的不是切点方法,通过不同的通知方法,将需要进行验证的方法拦截下来,并观察执行的结果有何不同。

目标:学会切面、切点、连接点、通知的使用;

           理解AspectJ 支持的三种通配符,以及五类通知的执行逻辑;

           简单了解 AOP 实现原理;

1. 添加 Spring Boot AOP 依赖:

        注意:这里添加的一定是 boot 的 aop依赖,而不是 spring 的 aop 依赖;


	   org.springframework.boot
	   spring-boot-starter-web

2. 创建切面:

        创建切面需要引入 @Aspect 注解,以及容器相关五大类注解之一;

@Aspect 注解的作用:        

  1. 用于标识一个类为切面类,Spring框架会将该类识别为一个切面,并根据其中的通知和切点等定义,自动将其织入到目标对象的方法中;
  2. 定义切点和通知:切面类中可以定义切点和通知等相关内容,通过@Pointcut、@Before、@After等注解来实现。切点用于指定在哪些连接点上应用通知;

Spring AOP_第1张图片

当前这个 UserAOP 类就作为一个切面类,在该切面内,将实现注册拦截规则,通知方法等功能;

3. 创建切点:

        在切面 UserAOP 中创建切点:

        切点用来定义拦截规则,写一个空方法,不实现,加上注解,实现在通知中,拦截规则精确到参数部分;

Spring AOP_第2张图片

 说明:

  • execution 表示执行;

  • 这里 execution 里面的路径表示要拦截的是UserController类里所有的方法,如果要拦截具体的方法,就注明其方法名;

  • *(..) 表示参数是什么:

    • 如果是 .. 表示动态参数,要拦截的方法传参是任意参数的;

    • 如果写作 *(int,String)  就表示拦截传参顺序为先 int 后 String 的方法;

  • pointcut() 方法说明了后续只要调用这个方法,就要按照注解逻辑执行拦截操作;

关于这里具体参数的规则,将在下面 理解切点表达式的规则 中详细介绍。 

4. 创建通知:

        通知用来拦截下来具体要做什么,比如可以验证当前用户是否登录、验证身份信息是否正确、验证用户状态是否安全等等。就好比交警在路上拦下人,要检查一下驾驶证一样。

通知的类型有五种:

  1. 前置通知(@Before):在目标方法执行之前执行的通知。可以在前置通知中进行一些准备工作,例如参数校验、权限验证等。

  2. 后置通知(@After):在目标方法执行之后执行的通知。无论目标方法是否抛出异常,后置通知都会执行。可以在后置通知中进行一些清理工作,例如释放资源、记录日志等。

  3. 返回通知(@AfterReturning):在目标方法正常返回后执行的通知。可以获取目标方法的返回值,并对返回值进行处理或记录。

  4. 异常通知(@AfterThrowing):在目标方法抛出异常后执行的通知。可以捕获目标方法抛出的异常,并进行相应的处理,例如发送告警、记录日志等。

  5. 环绕通知(@Around):在目标方法执行前后都执行的通知。可以完全控制目标方法的执行过程,包括是否调用目标方法、修改参数、修改返回值等。

        这五种通知类型可以根据需要选择使用,用于在目标方法的不同阶段执行相应的操作。可以根据具体的业务需求,选择合适的通知类型来实现横切关注点的功能。

下面我们按照:前置通知 —— 后置通知 —— 环绕通知 —— 返回通知 —— 异常通知 的顺序,验证它们各自的逻辑,以及相互的关系。

(1)继续在刚才 UserAOP 切面中添加一个前置通知:

        前置方法使用 @Before 注解,注解里注明切点方法;

Spring AOP_第3张图片

5. 创建连接点:

        因为连接点是会触发拦截规则的,也就是触发切点方法,因此根据先前设定好的要拦截的方法,添加一个类 UserController,这个类中的所有方法都将被拦截到:

Spring AOP_第4张图片

 显而易见,该类中有两个方法:sayHi() 和 login() 都作为连接点;

另外我们在创建一个非连接点,用于对比验证:

添加一个类 ArticleController,也实现一个方法 sayHi() :

Spring AOP_第5张图片

执行:

        到此,就可以执行对比结果了:

Spring AOP_第6张图片

 这里要提醒的是,由于本篇只是介绍和了解AOP思想及执行逻辑,所以通知方法中不涉及到具体的业务功能,只是一个简单的日志看一下它是如何执行的。

演示后置通知:

        继续在切面类 UserAOP 中添加一个后置通知,使用 @After() 注解:

Spring AOP_第7张图片

 如下图:由于此时前置通知和后置通知都存在,所以会按照:【前置通知 —— 目标方法 —— 后置通知】 的顺序走一遍:

Spring AOP_第8张图片

演示环绕通知:

        使用环绕通知,可以在目标方法执行之前和之后都执行自定义的逻辑

  1. 在环绕通知方法中,通过ProceedingJoinPoint 来给环绕通知方法传入目标方法本身。

  2. 可以使用 proceed() 方法来调用目标方法,也可以选择不调用,从而实现拦截目标方法的效果。

  3. 在调用 proceed() 方法之后,可以获取目标方法的返回值,并进行相应的处理或记录。

  4. 如果目标方法抛出异常,环绕通知方法可以捕获并进行相应的处理,例如发送告警、记录日志等。

        继续在切面类 UserAOP 中添加一个环绕通知方法,使用 @Around 注解: 

Spring AOP_第9张图片

此时,前置通知、后置通知、环绕通知三者同时存在的情况下,看如何执行的:

按照:开始环绕通知 —— 前置通知 ——  目标方法 —— 后置通知 —— 结束环绕通知 的顺序执行

Spring AOP_第10张图片

 如果去掉前置通知合成后置通知,只有环绕通知的情况下:

Spring AOP_第11张图片

 


理解切点表达式的规则

        在上面环节中,有一个环节是创建切点,它在定义拦截规则时,execution 后面的参数是一个长表达式,这里就会展开介绍。

        切点表达式由切点函数组成,execution、within、this、target、args等,其中execution()是最常用的切点函数,用来匹配方法,语法为:

execution(<修饰符><返回类型><包.类.方法(参数)><异常>)

  修饰符和异常一般可以省略:

 AspectJ 支持的三种通配符:

 * :匹配任意字符,只匹配⼀个元素(包,类,或方法,方法参数);

 .. :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使用;

 + :表示按照类型匹配指定类的所有子类(包括自身),必须跟在类名后面,如 com.cad.Car+ ,表示继承该类的 所有子类包括本身;

下面用具体例子分析:

  • execution(* com.cad.demo.User.*(..)) :匹配 User 类里的所有方法。
  • execution(* com.cad.demo.User+.*(..)) :匹配该类的子类包括该类的所有方法。
  • execution(* com.cad.*.*(..)) :匹配 com.cad 包下的所有类的所有方法。
  • execution(* com.cad..*.*(..)) :匹配 com.cad 包下、子孙包下所有类的所有方法。
  • execution(* addUser(String, int)) :匹配 addUser 方法,且第⼀个参数类型是 String,第⼆个 参数类型是 int。

Spring AOP 的实现原理:

Spring AOP的实现原理可以用一个生活中的例子来解释: 

假设你是一个家庭主妇,每天要做很多家务,比如做饭、洗衣服、打扫卫生等。但是你发现每次做饭的时候,都需要先洗菜、切菜、炒菜等一系列的操作,这些操作其实是和每一顿饭都有关系的,而不仅仅是某一顿饭。

于是你决定引入一个帮手来帮你处理这些和做饭相关的操作,这个帮手就是Spring AOP。它的实现原理就是在你做饭的过程中,帮你自动完成洗菜、切菜、炒菜等操作,而不需要你每次都亲自去做。

通过引入帮手,你不仅解放了自己的双手,还能更专注于做好饭菜本身。同样地,Spring AOP通过动态生成代理对象,在目标方法执行前、执行后或抛出异常时,自动插入相应的通知,帮助开发者更专注于业务逻辑的实现,而不需要关心横切关注点的处理。

具体来说,Spring AOP实现原理包括以下几个步骤:

  1. 定义切面:通过编写切面类,使用注解或XML配置来定义通知和切点表达式。

  2. 创建代理对象:当Spring容器启动时,会扫描并解析切面类,根据切面类中的通知和切点表达式,动态生成代理对象。

  3. 代理对象的生成方式有两种:

    • 基于JDK动态代理:如果目标对象实现了接口(因为JDK动态代理是基于接口的代理方式,使用目标对象至少要实现一个接口,这个接口可以是自定义的接口,也可以是Spring提供的AOP相关接口),Spring AOP会使用JDK动态代理来生成代理对象。在运行时,通过实现InvocationHandler接口,通过反射,将目标对象的方法调用委托给通知对象。

    • 基于CGLIB动态代理:如果目标对象没有实现接口,Spring AOP会使用CGLIB动态代理来生成代理对象。在运行时,通过继承目标对象的子类,并重写其方法,将方法调用委托给通知对象。

  4. 代理对象的应用:当目标对象的方法被调用时,代理对象会拦截方法调用,并根据切点表达式判断是否应用通知。如果切点表达式匹配成功,则会在目标方法执行前、执行后或抛出异常时,调用相应的通知方法。

总之,Spring AOP的实现原理是基于动态代理的方式。通过动态生成代理对象,并在目标方法调用时拦截并应用通知,实现对目标对象的增强。这种方式使得开发人员可以将横切关注点与业务逻辑分离,提高了代码的可维护性和可重用性。

你可能感兴趣的:(Java知识分享,spring,mysql,java)