• 0
  • 0
分享
  • Spring中的AOP——软甲测试圈
  • 恬恬圈 2021-08-19 15:56:20 字数 10450 阅读 1284 收藏 0

1、AOP相关术语

Joinpoint(连接点):

所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。(业务层接口中所有的方法)

Pointcut(切入点):

所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义(被增强的方法)所有的切入点都是连接点。

Advice(通知/增强):

所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知,通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

1.png

Introduction(引介):

引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。

Target(目标对象):

代理的目标对象(被代理对象)

Weaving(织入):

是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。

Proxy(代理):

一个类被 AOP 织入增强后,就产生一个结果代理类(return返回的就是代理对象)

Aspect(切面):

是切入点和通知(引介)的结合。

学习spring中的AOP要明确的事

a、开发阶段(我们做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
b、运行阶段(Spring 框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对
象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行

Spring的基于Xml的AOP

1、导入jar包

2.png

2、创建配置文件导入aop的约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

3、在配置好spring的IOC后,开始配置通知类

 <!--配置spring的IOC,把service对象配置进来-->
    <bean id="accountService" class="com.itheima.service.Impl.AccountServiceImpl"></bean>
    <!--配置Logger类(通知类)-->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>

4、配置AOP

<!--配置AOP-->
    <aop:config>
        <!--配置切入点表达式,id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
              此标签写在aop:aspect标签内部只能当前切面使用
              它还可以写在aop:aspect标签外面,此时就变成了所有切面可用(但要写在aop:aspect标签上面)
              -->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.Impl.*.*(..))"/>
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <!--配置前置通知:在切入点方法执行之前执行-->
      <!--      <aop:before method="bforePrintLog" pointcut-ref="pt1"></aop:before>-->
            <!--配置后置通知:在切入点方法正常执行之后执行。它和异常通知永远只能执行一个-->
           <!-- <aop:after-returning method="afterReturnPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
            <!--配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
           <!-- <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
            <!--配置最终通知:无论切入点方法是否正常执行它都会在其后执行-->
            <!--<aop:after method="afterFinalPrintLog" pointcut-ref="pt1"></aop:after>-->
            <!--配置环绕通知-->
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>

spring中基于XML的AOP配置步骤

  • 把通知bean(Logger)也交给spring来管理;

  • 使用aop:config标签表明配置开始AOP配置;

  • 使用aop:aspect标签表明配置切面;

  1. id属性:是给切面提供一个唯一标识;

  2. ref属性:是指定通知类bean的id。

  • 在aop:aspect标签的内部使用对应标签来配置通知的类型;

我们现在示例是让printLog方法在切入点方法执行之前执行,所以是前置通知,

aop:before:表示配置前置通知

method属性:用于指定Logger类中哪个方法是前置通知

pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

流程解释:

3.png

切点表达式的写法:

        切入点表达式的写法:
                关键字:execution(表达式)
                表达式:
                    访问修饰符   返回值   包名.包名.包名...类名.方法名(参数列表)
                 标准的表达式写法:
                      public  void  com.itheima.service.Impl.AccountServiceImpl.save()
                  访问修饰符可以省略
                        void  com.itheima.service.Impl.AccountServiceImpl.save()
                  返回值可以使用通配符,表示任意返回值
                        *   com.itheima.service.Impl.AccountServiceImpl.save()
                   包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
                        *  *.*.*.*.AccountServiceImpl.save()
                   包名也可以使用..表示当前包及其子包
                        *  *..AccountServiceImpl.save()
                   类名和方法名都可以使用*来实现通配
                       *   *..*.()
                    参数列表:
                        可以直接写数据类型:
                                基本类型写名称   int
                                引用类型写包名.类名的方式   java.lang.String
                         可以使用通配符表示任意类型,但是必须有参数
                         可以使用..表示有无参数均可,有参数可以是任意类型
                     全通配写法:
                        * *..*.*(..)
                       实际开发中切入点表达式的通常写法:
                            切到业务层实现类下的所有方法
                                * com.itheima.service.Impl.*.*(..)

四种通知配置

配置前置通知:在切入点方法执行之前执行
<aop:before method=“bforePrintLog” pointcut-ref=“pt1”>< /aop:before>
配置后置通知:在切入点方法正常执行之后执行。它和异常通知永远只能执行一个
<aop:after-returning method=“afterReturnPrintLog” pointcut-ref=“pt1”>< /aop:after-returning>
配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
<aop:after-throwing method=“afterThrowingPrintLog” pointcut-ref=“pt1”>< /aop:after-throwing>
配置最终通知:无论切入点方法是否正常执行它都会在其后执行
<aop:after method=“afterFinalPrintLog” pointcut-ref=“pt1”>< /aop:after>
<!--配置AOP-->
<aop:config>
    <!--配置切入点表达式,id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
          此标签写在aop:aspect标签内部只能当前切面使用
          它还可以写在aop:aspect标签外面,此时就变成了所有切面可用(但要写在aop:aspect标签上面)
          -->
    <aop:pointcut id="pt1" expression="execution(* com.itheima.service.Impl.*.*(..))"/>
    <aop:aspect id="logAdvice" ref="logger">
        <!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
        
        <!--配置前置通知:在切入点方法执行之前执行-->
        <aop:before method="bforePrintLog" pointcut-ref="pt1"></aop:before>
        <!--配置后置通知:在切入点方法正常执行之后执行。它和异常通知永远只能执行一个-->
        <aop:after-returning method="afterReturnPrintLog" pointcut-ref="pt1"></aop:after-returning>
        <!--配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
        <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
        <!--配置最终通知:无论切入点方法是否正常执行它都会在其后执行-->
        <aop:after method="afterFinalPrintLog" pointcut-ref="pt1"></aop:after>
    </aop:aspect>
</aop:config>

之前能用,改个位置就不能用,要考虑约束(顺序和位置)

<!--配置切入点表达式,id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
      此标签写在aop:aspect标签内部只能当前切面使用
      它还可以写在aop:aspect标签外面,此时就变成了所有切面可用(但要写在aop:aspect标签上面)
      -->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.Impl.*.*(..))"/>

配置环绕通知

环绕通知

问题:

当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。(即发现执行as.save()方法后只输出了printLog开始记录日志了。。。。,未输出save方法中的语句)

分析:

通过对比动态代理中的环挠通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。

4.png

解决:

Spring框架为我们提供了一个接口,ProceedingJoinPoint。 该接口有个方法proceed(), 此方法就相当于明确调用切入点方法 。
该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会 为我们提供该接口的实现类供我们使用。

spring中的环绕通知:

它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。

代码演示:

package com.itheima.utils;
import org.aspectj.lang.ProceedingJoinPoint;
/*
* 用于记录日志的工具类,它里面提供了公共的代码
* */
public class Logger {
    /*
    * 用于打印日志,计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
    * 一、可以使用动态代理的方式,创建一个代理对象,在调用业务层方法之前调用printLog方法
    * 二、使用spring配置
    * */
    /*前置通知*/
    public void bforePrintLog(){
        System.out.println("前置通知的bforePrintLog方法开始记录日志了。。。。");
    }
    /*后置通知*/
    public void afterReturnPrintLog(){
        System.out.println("后置通知的afterReturnPrintLog方法开始记录日志了。。。。");
    }
    /*异常通知*/
    public void afterThrowingPrintLog(){
        System.out.println("异常通知的afterThrowingPrintLog方法开始记录日志了。。。。");
    }
    /*最终通知*/
    public void afterFinalPrintLog(){
        System.out.println("最终通知的afterFinalPrintLog方法开始记录日志了。。。。");
    }
    /*
     * 环绕通知*/
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue=null;
        try {
            Object[] args = pjp.getArgs();//获取方法所需参数
            System.out.println("printLog开始记录日志了。。。。前置");
            rtValue= pjp.proceed(args); //明确调用业务层方法(切入点方法)
            System.out.println("printLog开始记录日志了。。。。后置");
            return rtValue;
        } catch (Throwable t) {
            System.out.println("printLog开始记录日志了。。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("printLog开始记录日志了。。。。最终");
        }
    }
}

注解的AOP

1、配置文件的约束修改以及相关配置

5.png

<!--配置spring开启注解的AOP的支持-->
 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

2、在配置好spring的Ioc后,在通知类上使用@Aspect注解声明为切面

@Component("logger")
@Aspect  //表示当前类是一个切面类
public class Logger {

3、在增强的方法上使用注解配置通知

@Before
作用:
把当前方法看成是前置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@AfterReturning
作用:
把当前方法看成是后置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
@AfterThrowing
作用:
把当前方法看成是异常通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
@After
作用:
把当前方法看成是最终通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
@Around
作用:
把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
package com.itheima.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/*
* 用于记录日志的工具类,它里面提供了公共的代码
* */
@Component("logger")
@Aspect  //表示当前类是一个切面类
public class Logger {
    /*
    * 用于打印日志,计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
    * 一、可以使用动态代理的方式,创建一个代理对象,在调用业务层方法之前调用printLog方法
    * 二、使用spring配置
    * */
    @Pointcut("execution(* com.itheima.service.Impl.*.*(..))")
    public void pt1(){
    }
    @Before("pt1()")
    /*前置通知*/
    public void bforePrintLog(){
        System.out.println("前置通知的bforePrintLog方法开始记录日志了。。。。");
    }
    @AfterReturning
    /*后置通知*/
    public void afterReturnPrintLog(){
        System.out.println("后置通知的afterReturnPrintLog方法开始记录日志了。。。。");
    }
    @AfterThrowing
    /*异常通知*/
    public void afterThrowingPrintLog(){
        System.out.println("异常通知的afterThrowingPrintLog方法开始记录日志了。。。。");
    }
    @After("pt1()")
    /*最终通知*/
    public void afterFinalPrintLog(){
        System.out.println("最终通知的afterFinalPrintLog方法开始记录日志了。。。。");
    }
    /*
     * 环绕通知*/
   // @Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue=null;
        try {
            Object[] args = pjp.getArgs();//获取方法所需参数
            System.out.println("printLog开始记录日志了。。。。前置");
            rtValue= pjp.proceed(args); //明确调用业务层方法(切入点方法)
            System.out.println("printLog开始记录日志了。。。。后置");
            return rtValue;
        } catch (Throwable t) {
            System.out.println("printLog开始记录日志了。。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("printLog开始记录日志了。。。。最终");
        }
    }
}

使用注解在四种通知的调用顺序上会有差错,但是自己写的环绕通知没有

引用时带上方法名和小括号()

切入点表达式注解

@Pointcut
作用:
指定切入点表达式
属性
value:指定表达式的内容

使用方法:

@Pointcut("execution(* com.itheima.service.Impl.*.*(..))")
    public void pt1(){
    }
    @Before("pt1()")  //注意:千万别忘了写括号
    /*前置通知*/
    public void bforePrintLog(){
        System.out.println("前置通知的bforePrintLog方法开始记录日志了。。。。");
    }

5 不使用 XML 的配置方式

@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

总结

因为转账中出现的错误,我们加上了事务控制,但是这样业务层的代码太臃肿,为了解决这个臃肿:把这些重复的代码抽取出来再调用,但是这样会出现方法之间的依赖。

为了将这些重复的代码在方法执行时加进去,想到了动态代理。因为动态代理的特点就是:在不改变源码的基础上,对已有方法进行增强。

所以动态代理的回顾及子类动态代理,上面都是思想和实现的过程,下面的是以后经常用到

最后用spring来实现对方法的增强,

spring的AOP:

创建代理对象都是固定的,怎么对方法进行增强呢?

7.png

上面是通过配的方式告诉spring:

1、增强的代码在哪?

在txManager里面

2、对谁增强?

对accountService进行增强

3、在什么时候增强?

看通知的类型(前置、后置)


作者:你好y

原文链接:https://blog.csdn.net/qq_43430343/article/details/118972492

  • 【留下美好印记】
    赞赏支持
登录 后发表评论
+ 关注

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          •   对测试自动化的依赖性增加导致大量自动化软件测试工具的出现,使得很难确定哪些是最好的。为了帮助您完成自动化工作,我们根据自己和他人的经验创建了五大最佳自动化软件测试工具列表。  1. Selenium  Selenium可以说是web开发人员和测试人员中最受欢迎的自动化软件测试工具。它于2004年首次发布,从那时起它发生了很大变化。最新版本的Selenium包含几个组件,包括Selenium IDE和Selenium WebDriver。  Selenium IDE是一个用于Selenium测试的完整集成开发环境(IDE),主要用于创建快速错误复制脚本和脚本,以帮助进行自动化辅助探索性测试。...
            0 0 238
            分享
          • 一、软件测试的生命周期(软件测试的流程是什么?)需求分析——测试计划——测试设计/开发——测试执行——测试评估需求分析对需求进行合理化筛选,分析需求对需求明确细化测试计划: 测试进行的人员、时间、测试范围、测试目的等具体进行计划测试设计/开发: 根据需求提炼出的功能点开发测试用例测试执行 执行测试用例 找BUG 回归测试测试评估 评估本次测试的情况二、如何描述一个BUG?首先BUG就是和需求分析说明书中不匹配的功能,我们在实际测试中就需要将测出来的BUG记录在BUG管理工具(禅道,tapd,jira)里,以便开发人员查看,为了能让开发人员更能清楚的了解到BUG,我们就要规范书写BUG,包含以下...
            0 0 1489
            分享
          •   我们有时候做Postman接口测试时,需要操作数据库,比如:查询、新增、更新、删除数据等。这时就需要连接数据库,以MySQL数据库为例子,根据自己的总结,分享一下Postman连接数据库,以及对数据库中数据的操作步骤。  Postman要连接MySQL数据库,需要先安装xMySQL,并启动xMySQL服务,然后才可以调用。  安装xMySQL  在安装xMySQL之前,要先完成一些预置条件。  完成nodejs的安装  下载并安装nodejs:https://nodejs.org/dist/v12.16.3/node-v12.16.3-x64.msi ,下载nodejs后一键安装。  安装...
            0 2 3196
            分享
          • Shell不仅是一种命令解释器,还是一种编程语言,非常擅长处理文本类型的数据。由于Linux系统中的所有配置文件都是纯文本的,所以shell编程语言在Linux系统使用中发挥了巨大的作用。使用shell编写的程序类似于DOS下的批处理程序,简单来说,shell编程就是对一系列Linux命令的逻辑化处理。例如,我们在进行自动化测试的时候,需要将代码部署到服务器进行管理和集成,每次都要执行拉取最新的代码、编译打包、上传服务器一系列的步骤,效率不高而且很繁琐。我们将这个过程写成一个shell脚本,每次只需要运行一下这个脚本即可完成上述步骤,简单方便,提高工作效率。一、什么是shell脚本?我们可以在...
            2 2 2308
            分享
          • 导出json文件方便使用jenkins集成环境管理,导出python的话方便在linux系统下运行脚本。1、postman导出json文件:目前postman支持V1(逐渐弃用),V2,V2.1(推荐使用),只有客户端支持导出功能,chrome插件不支持选择要导出的版本号即可2、postman导出Python脚本生成后复制代码新建.py文件即可作者:笑笑就好90原文链接:https://www.cnblogs.com/xulinmei/p/10719231.html
            0 0 1039
            分享
      • 51testing软件测试圈微信