当前位置: 首页 > news >正文

Spring AOP基础动态代理基于JDK动态代理实现

目录

1. 预备知识-动态代理

1.1 什么是动态代理

1.2 动态代理的优势

1.3 基于JDK动态代理实现

2. AOP

2.1 基本概念

2.2 AOP带来的好处

3. Spring AOP

3.1 前置通知

3.2 后置通知

3.3 环绕通知

3.4 异常通知

3.5 适配器


1. 预备知识-动态代理

1.1 什么是动态代理

动态代理利用Java的反射技术(Java Reflection)生成字节码,在运行时创建一个实现某些给定接口的新类(也称"动态代理类")及其实例。

1.2 动态代理的优势

动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法就可以)
spring中的AOP是动态代理使用的经典场景。

1.3 基于JDK动态代理实现

在基于JDK的动态代理的实现中有两个重要的类:InvocationHandler, Proxy

  • InvocationHandler
    是代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
  • Proxy
    JDK中动态生成代理类的工具类

一个动态代理的示例:

定义一个接口(基于JDK的动态代理只能使用接口)

public interface ISubject {void hello(String param);
}

为接口定义实现类

public class SubjectImpl implements ISubject {@Overridepublic void hello(String param) {System.out.println("hello  " + param);}
}

实现一个代理类:

public class JDKProxy implements InvocationHandler {private Object target;public JDKProxy(Object target) {this.target = target;}//创建代理public Object newProxy() {return (ISubject)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}@Override   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("---------- 在业务方法调用之前可以进行前置增强   ------------");//利用反射机制调用方法,invoke为返回值,如果没有返回nullObject invoke = method.invoke(target, args);System.out.println("---------- 在业务方法调用之前可以进行后置增强   ------------");return invoke;}}

编写代理类实际的调用,利用Proxy类创建代理之后的Subject类

public class JDKProxyDemo {public static void main(String[] args) {ISubject subject = new SubjectImpl();JDKProxy subjectProxy = new JDKProxy(subject);ISubject proxyInstance = (ISubject)subjectProxy.newProxy();proxyInstance.hello("world");}}

运行结果:

---------- 在业务方法调用之前可以进行前置增强   ------------
hello  world
---------- 在业务方法调用之前可以进行后置增强   ------------

2. AOP

2.1 基本概念

  • 连接点 (Joinpoint)
    程序执行过程中明确的点,如方法的调用,或者异常的抛出.
  • 目标(Target)
    被通知(被代理)的对象,如上例中的SubjectImpl
  • 通知(Advice)
    在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理),可以理解为AOP真正要实现的功能
  • 代理(Proxy)
    将通知应用到目标对象后创建的对象(代理=目标+通知),请注意:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的,如上例中的JDKProxy
  • 切入点(Pointcut)
    多个连接点的集合,定义了通知应该应用到那些连接点。也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序
  • 适配器(Advisor)
    适配器=通知(Advice)+切入点(Pointcut)

AOP运行原理:目标对象只负责业务逻辑,通知只负责AOP增强逻辑(如日志,数据验证等),而代理对象则将业务逻辑而AOP增强代码组织起来(组织者)

2.2 AOP带来的好处

AOP是公用的框架代码放置的理想地方,将公共代码与业务代码分离,使我们在处理业务时可以专心的处理业务。
伪代码:

public void doSameBusiness (long lParam,String sParam){// 记录日志log.info("调用 doSameBusiness方法,参数是:"+lParam);// 输入合法性验证if (lParam<=0){throws new IllegalArgumentException("xx应该大于0");}if (sParam==null || sParam.trim().equals("")){throws new IllegalArgumentException("xx不能为空");}// 异常处理try{ 真正的业务处理}catch(...){}catch(...){}// 事务控制tx.commit();}

通过使用AOP我们可以将日志记录,数据合法性验证,异常处理等功能放入AOP中,那么在编写业务时就可以专心实现真正的业务逻辑代码。

3. Spring AOP

在spring中org.springframework.aop.framework.ProxyFactoryBean用来创建代理对象,在一般情况下它需要注入一下三个属性:

  • proxyInterfaces 代理应该实现的接口列表(List)
  • interceptorNames 需要应用到目标对象上的通知Bean的名字
  • target 目标对象 (Object)

准备工作:创建一个IBookService接口及其实现类,用于演示spring AOP开发示例:

public interface IBookService {// 购书public boolean buy(String userName, String bookName, Double price);// 发表书评public void comment(String userName, String comments);}
public class BookServiceImpl implements IBookService {private Logger logger = LoggerFactory.getLogger(this.getClass());public BookServiceImpl() {super();}public boolean buy(String userName, String bookName, Double price) {//logger.info("userName={},bookName={},price={}", userName, bookName, price);// 通过控制台的输出方式模拟购书logger.info(userName + " buy " + bookName + ", spend " + price);return true;}public void comment(String userName, String comments) {logger.info(userName + " say:" + comments);}}

将service配置到spring配置文件中,以便于被spring管理,按自己的实际情况配置

<!-- 目标 --><bean id="bookServiceTarget" class="com.zking.sp02.impl.BookServiceImpl"/>

3.1 前置通知

前置通知需要实现org.springframework.aop.MethodBeforeAdvice,前置通知将在目标对象调用前调用。示例实现购书系统AOP方式实现日志,简单打印调用的方法及参数

1)首先实现一个前置通知类,实现接口MethodBeforeAdvice,并实现接口中的before方法

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {private Logger logger  = LoggerFactory.getLogger(this.getClass());@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {String s = "[前置通知]: " + this.getClass() + "." + method.getName() + "将被调用,参数为:" + Arrays.toString(args);logger.info(s);}}

2)将实现的前置通知配置到Spring.xml中,一遍与被spring管理。需要根据自己的实际情况配置。

<bean id="myMethodBeforeAdvice" class="com.zking.springdemo.aop.MyMethodBeforeAdvice"/>

3)现在需要解决如何将通知和目标联系起来,需要一个组织者 - 代理

<bean id="bookService" class="org.springframework.aop.framework.ProxyFactoryBean"><!-- 配置代理目标 --><property name="target" ref="bookServiceTarget"/><!-- 配置拦截器列表,拦截器就是通知 --><property name="interceptorNames"><list><value>myMethodBeforeAdvice</value></list></property><!-- 代理要实现的接口,代理类与被代理类需要实现相同接口 --><property name="proxyInterfaces"><list><value>com.zking.springdemo.aop.IBookService</value></list></property></bean>

写一个测试类,测试前置通知

public class Demo {public static void main(String[] args) {ApplicationContext <u>cxt</u> = new ClassPathXmlApplicationContext("/spring.xml");IBookService bookService = (IBookService)cxt.getBean("bookService");System.out.println(bookService.getClass().getName());        bookService.buy("zs", "hlm", 10D);}}

3.2 后置通知

在连接点正常完成后执行的通知。定义的后置通知类需要实org.springframework.aop.AfterReturningAdvice

示例:在线购书系统中,要求不修改BookServiceImpl代码的情况下增加如下功能:对买书的用户进行返利:每买本书返利10元,简单打印类似于“[后置通知] 返利10元”即可。开发步骤与前置通知类似

1) 编写一个后置通知实现类

public class MyAfterReturnAdvice implements AfterReturningAdvice {private Logger logger = LoggerFactory.getLogger(this.getClass());@Overridepublic void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {       logger.info("[后置通知]:  返利10元");        }
}

2) 将后置通知实现类配置到spring配置文件中,以便于spring管理

<bean id="myAfterReturnAdvice" class="com.zking.springdemo.aop.MyAfterReturnAdvice"/>

3)解决如何将通知和目标联系起来,在实现前置通知时已经配置了代理对象,现在只要将后置通知也配置到拦截器列表当中即可。

<!-- 配置拦截器列表,拦截器就是通知 -->
<property name="interceptorNames"><list><!-- 前置通知 --><value>myMethodBeforeAdvice</value><!-- 后置通知 --><value>myAfterReturnAdvice</value></list>
</property>

运行上例已经实现的测试类,查看后置通知的运行效果。

3.3 环绕通知

包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能很强大。自定义的环绕通知需要实现org.aopalliance.intercept.MethodInterceptor接口。
示例:在环绕通知中输出日志和返回值

1)实现一个环绕通知,该类要实现MethodInterceptor接口

public class MyMethodInterceptor implements MethodInterceptor {private Logger logger = LoggerFactory.getLogger(this.getClass());@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {//获取目标对象Object target = invocation.getThis();//获取参数Object[] args = invocation.getArguments();//获取方法Method method = invocation.getMethod();logger.info("[环绕通知] 前:将调用{}.{}方法,参数为{}",target.getClass(),method.getName(), Arrays.toString(args));//调用目标对象上的方法   Object val = invocation.proceed();logger.info("[环绕通知] 后,已调用{}.{}, 返回值:{}", target.getClass(),method.getName(), val);return val;}}

2)在spring的配置文件中配置环绕通知,以便于spring管理

<bean id="myMethodInterceptor" class="com.zking.springdemo.aop.MyMethodInterceptor"/>

3)解决如何将通知和目标联系起来,在实现前置通知时已经配置了代理对象,现在只要将环绕通知也配置到拦截器列表当中即可。

<!-- 配置拦截器列表,拦截器就是通知 -->
<property name="interceptorNames"><list><!--前置通知--><value>myMethodBeforeAdvice</value><!--后置通知--><value>myAfterReturnAdvice</value><!--环绕通知--><value>myMethodInterceptor</value></list>
</property>

运行上例已经实现的测试类,查看后置通知的运行效果。

3.4 异常通知

异常通知需要实现ThrowsAdvice接口,这个通知会在方法抛出异常退出时执行,与以上演示的前置、后置、环绕通知不同,主要有一下特点:

  • 这个接口里面没有定义方法,要求我们的类必须实现afterThrows这个方法
  • 以异常类型作为参数,无返回值

示例
1)定义一个自定义异常,继承RuntimeException运行时异常

public class PriceException extends RuntimeException {public PriceException() {super();}public PriceException(String message, Throwable cause) {super(message, cause);}public PriceException(String message) {super(message);}public PriceException(Throwable cause) {super(cause);}
}

2)创建异常通知类,该类实现ThrowsAdvice接口,并实现afterThrowing方法

public class MyThrowsAdvice implements ThrowsAdvice {private Logger logger = LoggerFactory.getLogger(this.getClass());//以异常类型作为参数,无返回值public void afterThrowing(PriceException e) {logger.info("程序发生了PriceException异常");}}

剩下的步骤是将异常通知配置到spring配置文件中,及在代理配置中加入异常通知的配置,可参考上面的环绕通知等示例。

3.5 适配器

适配器, 通过正则表达式来定义方法切入点,也就是说定义哪些方法将被拦截器处理。适配器=通知(Advice)+切入点(Pointcut)。
在配置适配器时需要使用org.springframework.aop.support.RegexpMethodPointcutAdvisor

配置适配器示例:

<!-- 配置适配器 -->
<bean id="myAdisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"><!-- 定义正则表达式,定义需要拦截的方法名 ,本例定义了所有以buy结尾的方法  --><property name="patterns"><list><value>.*buy</value></list></property><!-- 定义由那个通知(或者叫拦截器)来处理匹配的方法 --><property name="advice"><ref bean="myAfterReturnAdvice"/></property>
</bean>

在代理中使用刚刚配置的适配器

<!-- 将直接使用后置拦截器 ,改为使用适配器 -->
<!-- <value>myMethodAfterReturnAdvice</value> --><!-- 通过适配器使用后置拦截器 -->
<value>myAdisor</value>

修改代理中的拦截器列表(spring配置文件,代理部分),将配置器直接配置在拦截器列表中即可。

相关文章:

Spring AOP基础动态代理基于JDK动态代理实现

目录 1. 预备知识-动态代理 1.1 什么是动态代理 1.2 动态代理的优势 1.3 基于JDK动态代理实现 2. AOP 2.1 基本概念 2.2 AOP带来的好处 3. Spring AOP 3.1 前置通知 3.2 后置通知 3.3 环绕通知 3.4 异常通知 3.5 适配器 1. 预备知识-动态代理 1.1 什么是动态代理…...

第一章 计算机系统概述 五、中断和异常、系统调用

目录 一、中断的作用 二、中断的类型 1、内中断&#xff08;异常&#xff09; 2、外中断 三、中断机制的基本原理 四、系统调用 1、定义&#xff1a; 2、与库函数的区别 3、按功能分类 4、作用 一、中断的作用 1、“中断”是让操作系统内核夺回CPU使用权的唯一途径 …...

【C语言】文件操作(上)

一.什么是文件 文件是磁盘上的文件,文件中存放的数据不随程序的退出而销毁. 二.文件的打开与关闭 1.文件指针 每个被使用的文件都在内存中开辟了一个相应的文件信息区&#xff0c;用来存放文件的相关信息&#xff08;如文件的名字&#xff0c;文件状态及文件当前的位置等&…...

【Linux】让笔记本发挥余热,Ubuntu20.04设置WiFi热点

Ubuntu20.04设置WiFi热点 由于卧室距离客厅较远&#xff0c;wifi信号太弱&#xff0c;体验极差。鉴于卧室的笔记本电脑是通过网线连接的客厅路由器&#xff0c;因此考虑将这台老破笔记本作为“路由器”&#xff0c;以便发挥它的余热。实验证明&#xff0c;上网速度提升数十倍&a…...

【云平台】遥感地信云平台收录

文章目录 国内1 航天宏图PIE-Engine2 商汤科技3 AI Earth4 EarthDataMiner国外结语国内 1 航天宏图PIE-Engine https://engine.piesat.cn/live-show-list 在这里插入图片描述 2 商汤科技 https://senseearth-cloud.com/map 3 AI Earth https://engine-aiearth.aliyun.com…...

23种设计模式之---单例模式

闲来无事学一下设计模式&#xff0c;希望这23种可以一直更下去&#xff0c;什么时候能更完呢&#xff0c;也许一个月&#xff0c;也许一年&#xff0c;也许断更 设计模式六大原则 本文是23篇的第一篇&#xff0c;在学习设计模式之前&#xff0c;你需要了解下六大原则。 1、开…...

蓝桥杯官网练习题(纸牌三角形)

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 A,2,3,4,5,6,7,8,9 共 99 张纸牌排成一个正三角形&#xff08;A 按 1 计算&#xff09;。要求每个边的和相等。 下图就是一种排法。 这样的排法可能会有很多。 如果…...

一辆新能源汽车的诞生之旅:比亚迪常州工厂探营

作为在新能源汽车领域首屈一指的国产品牌&#xff0c;比亚迪近年来可以说是捷报频传&#xff0c;高奏凯歌。 以比亚迪常州工厂为例&#xff0c;据介绍该工厂当初规划设计时定下的生产目标&#xff0c;是年产量能够达到20万辆。然而在2023年上半年&#xff0c;该工厂光是主要销往…...

【算法专题突破】双指针 - 最大连续1的个数 III(11)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;1004. 最大连续1的个数 III - 力扣&#xff08;Leetcode&#xff09; 这道题不难理解&#xff0c;其实就是求出最长的连续是1的子数组&#xff0c; 但是&#xff0c;他支…...

java实现备忘录模式

备忘录模式是一种行为设计模式&#xff0c;它允许您捕获一个对象的内部状态&#xff0c;并在稍后的时间点将其恢复。这对于需要撤销操作或恢复到先前状态的应用程序非常有用。以下是在 Java 中实现备忘录模式的一般步骤&#xff1a; 创建一个原发器类&#xff08;Originator&am…...

aardio语言的通用数据表维护

import win.ui; /*DSG{{*/ var winform win.form(text"通用数据表维护";right617;bottom427;bgcolor15780518) winform.add( buttonAdd{cls"button";text"增加空行";left469;top40;right564;bottom80;flat1;z2}; buttonDel{cls"button&quo…...

手写RPC框架--7.封装响应

RPC框架-Gitee代码(麻烦点个Starred, 支持一下吧) RPC框架-GitHub代码(麻烦点个Starred, 支持一下吧) 封装响应 封装响应a.封装响应b.请求id生成器(雪花算法)c.抽象序列化d.建立序列化工厂e.hessian的序列化方式&#xff08;拓展&#xff09; 封装响应 a.封装响应 在core模块…...

Linux入门教程||Linux系统目录结构

登录系统后&#xff0c;在当前命令窗口下输入命令&#xff1a; ls / 你会看到如下图所示: 树状目录结构&#xff1a; 以下是对这些目录的解释&#xff1a; /bin&#xff1a; bin是Binary的缩写, 这个目录存放着最经常使用的命令。 /boot&#xff1a; 这里存放的是启动Linux时…...

LeetCode 88. 合并两个有序数组

文章目录 一、题目二、C# 题解 一、题目 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&a…...

C语言实现扫雷小游戏

1.首先扫雷游戏要存储布置好的雷信息&#xff0c;需要一个二维数组 不是雷放* 雷&#xff1a;# 不是雷&#xff1a;0 雷&#xff1a;1 2. 给2个二维数组 9*9 一个存放雷的信息&#xff0c;一个存放布置好雷的信息 3.为了防止在统计坐标周围的…...

【linux基础(五)】Linux中的开发工具(上)---yum和vim

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到开通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux中的开发工具 1. 前言2.…...

C++学习之list的实现

在了解学习list实现之前我们首先了解一下关于迭代器的分类&#xff1a; 按功能分类&#xff1a; 正向迭代器 反向迭代器 const正向迭代器 const反向迭代器 按性质分类&#xff1a; 单向迭代器 只能 例如单链表 双向迭代器 可&#xff0c;也可-- 例如双…...

一种高效且节约内存的聚合数据结构的实现

一种高效且节约内存的聚合数据结构的实现 在特定的场景中&#xff0c;特殊定制数据结构能够得到更加好的性能且更节约内存。 聚合函数GroupArray的问题 GroupArray聚合函数是将分组内容组成一个个数组&#xff0c;例如下面的例子&#xff1a; SELECT groupArray(concat(ABC…...

机器学习(10)---特征选择

文章目录 一、概述二、Filter过滤法2.1 过滤法说明2.2 方差过滤2.3 方差过滤对模型影响 三、相关性过滤3.1 卡方过滤3.2 F检验3.3 互信息法3.4 过滤法总结 四、Embedded嵌入法4.1 嵌入法说明4.2 以随机森林为例的嵌入法 五、Wrapper包装法5.1 包装法说明5.2 以随机森林为例的包…...

Python之数据库(MYSQL)连接

一&#xff09;数据库SQL语言基础 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Database…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7

在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤&#xff1a; 第一步&#xff1a; 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为&#xff1a; // 改为 v…...