• 0
  • 0
分享
  • Spring中的AOP——软甲测试圈
  • 恬恬圈 2021-08-19 15:56:20 字数 10450 阅读 1294 收藏 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 1 2281
            分享
          •   一、我们经常遇到的问题  在我们的测试日常中,不免会遇到网页性能感知测试的工作,比如:测试访问某个网页耗时。针对此类测试,笔者经历过两种简单的方法:测试网页使用的后端接口响应耗时等同为网页耗时;使用秒表计算网页响应耗时。  测试网页响应耗时,我们需要知道触发网页请求时,直至网页刷新、响应,整个过程到底包含了些什么。从大体来分,主要包含两部分耗时:后端接口请求响应耗时和前端页面资源加载耗时。而抛开前端页面资源加载时间,单纯将后端接口响应耗时等同为网页响应耗时是不准确的;而不清除浏览器本身资源配置和消耗,简单使用秒表计算网页响应耗时,结果是粗泛不准确的。  那么,我们应该怎么测试网页响应耗时呢...
            14 14 1223
            分享
          • 问题描述:19C数据库提示密码过期解决问题思路:设置密码永不过期前提登录服务器成功。su - dbadmin输入密码sqlplus /nologconn / as sysdba -alter session set container=XXXX切换容器select sys_context ('userenv','con_name') from dual;查询当前容器 alter profile default limit PASSWORD_LIFE_TIME unlimited;设置永不过期 alter user username ident...
            0 0 1307
            分享
          •   据百度地图官方昨日消息,百度地图将上线《黑神话:悟空》八戒导航语音包。  在《黑神话:悟空》中,八戒会在游戏第三章出现,并一路护送天命人降妖除魔,成为寻找大圣六根路上的得力助手。  玩家在百度地图搜索“黑神话”即可预约《黑神话:悟空》八戒导航语音包。作者:汪淼原文链接:IT之家(ithome.com)
            0 0 664
            分享
          • 软件测试(Software Testing):在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。一、软件的分类?1、按照功能划分:【系统软件】:如操作系统、数据库管理系统,各种驱动软件等;【应用软件】:如Office、有道翻译、QQ等;2、按照技术结构划分:【单机版本】:如Office,画图工具等;【C/S结构软件】:如QQ、微信等;【B/S结构软件】:如新浪、搜狐、google等;3、按照使用终端划分:【PC端】:电脑版QQ等安装在电脑端的软件;【移动端】:如手机版QQ、微信等安装在移动端的软件;4、按照用户划分:【产品软件】:Office、...
            0 0 1036
            分享
      • 51testing软件测试圈微信