GoF 代理模式

代理模式的理解

代理模式,就是自己做不了,需要别人来代理,代替自己来完成。最终这个行为还是要发生,只不过不是由自己来完成,而是由别人代理完成,只是对于客户其他人来说感受不到

代理模式的作用:

  1. 当一个对象需要受到保护时,可以考虑使用代理模式去完成某个行为。
  2. 需要给某个对象的功能进行增强时,可以考虑找一个代理进行增强。
  3. A 对象和 B 对象无法直接进行交互时,也可以使用代理模式来解决。

代理模式中的三大角色:

  1. 目标对象:需要被代理的对象
  2. 代理对象:代理目标对象的对象
  3. 目标对象和代理对象的公共接口:目标对象和代理对象之间应该具有相同的行为。为了使用户察觉不到是由代理对象完成的,使用户感觉还是由目标对象进行完成的

使用代理模式,对于客户端程序来说,客户端无法察觉,客户端在使用代理对象的时候,就像在使用目标对象。对于客户端程序来说,使用代理对象时就像在使用目标对象一样。

代理模式是GoF23种设计模式之一。属于结构型设计模式。

  • 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。

代理模式的代码实现有两种形式:

  • 静态代理
  • 动态代理

14.2 静态代理

项目经理提出一个新的需求:要统计所有业务接口中每一个业务方法的耗时。

解决方案一:硬编码,在每一个业务接口中的每一个业务方法中直接添加统计耗时的程序。

  • 缺点:
    • 缺点一:违背OCP开闭原则。
    • 缺点二:代码没有得到复用,相同的代码写了很多遍。

解决方案二:编写业务类的子类,让子类继承业务类,对每个业务方法进行重写。

  • 缺点一:虽然解决了OCP开闭原则。但是这种方式会导致耦合度很高,因为采用了继承关系。继承关系是一种耦合度非常高的关系,不建议使用。
  • 缺点二:代码没有得到复用,相同的代码写了很多遍。

解决方案三:代理模式。

  • 优点1:解决了OCP问题。
  • 优点2:采用代理模式的has a,可以降低耦合度。
  • 缺点:类爆炸,假设系统中有1000个接口,那么每个接口都需要对应的代理类,这样类会急剧膨胀,不好维护
公共接口
package cw.study.spring.service;/*** ClassName: OrderService* Package: cw.study.spring.service* Description:*/
public interface OrderService {/*** 生成订单*/void generate();/*** 修改订单*/void modify();/*** 查看订单详情*/void detail();}
目标对象
package cw.study.spring.service;/*** ClassName: OrderSeriveImpl* Package: cw.study.spring.service* Description:** @Author tcw* @Create 2023-05-28 9:34* @Version 1.0*/
public class OrderSeriveImpl implements OrderService {@Overridepublic void generate() {try {Thread.sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单生成成功...");}@Overridepublic void modify() {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单修改成功...");}@Overridepublic void detail() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单查询成功...");}
}
代理对象

代理对象和目标对象要具有相同的行为,就需要实现同样的接口,使得客户端在使用代理对象的时候,就像在使用目标对象一样

在代理对象中最终也还是要执行目标对象中的目标方法,为了能够在代理对象中执行目标对象的目标方法,我们可以将目标对象作为代理对象的属性,代理对象中要有目标对象的引用,这种关系为关联关系,耦合度比泛化关系

  • 关联关系:在A中,有B作为其属性,A has a B
  • 泛化关系:A继承B,A is a B

package cw.study.spring.service;/*** ClassName: OrderServiceProxy* Package: cw.study.spring.service* Description:* 代理对象** @Author tcw* @Create 2023-05-29 10:25* @Version 1.0*/
public class OrderServiceProxy implements OrderService {// 使用公共接口,公共接口的耦合度低private OrderService target;// 创建代理对象的时候,给代理对象中目标对象引用赋值public OrderServiceProxy(OrderService target) {this.target = target;}@Overridepublic void generate() {// 增强代码long begin = System.currentTimeMillis();// 调用目标对象的目标方法target.generate();long end = System.currentTimeMillis();System.out.println("执行耗时:" + (end - begin));}@Overridepublic void modify() {long begin = System.currentTimeMillis();// 调用目标对象的目标方法target.modify();long end = System.currentTimeMillis();System.out.println("执行耗时:" + (end - begin));}@Overridepublic void detail() {long begin = System.currentTimeMillis();// 调用目标对象的目标方法target.detail();long end = System.currentTimeMillis();System.out.println("执行耗时:" + (end - begin));}
}
public class Client {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderSeriveImpl();// 创建代理对象OrderService proxy = new OrderServiceProxy(target);// 使用代理对象的代理方法// 使用代理对象就像在使用目标对象一样,// 都可以完成相同的功能,并且还可以进行加强proxy.generate();proxy.modify();proxy.detail();}
}

使用静态代理模式,没有修改原先写好的类,符合OCP原则,且在代理类中只是有目标对象的引用,耦合度比前两种方法更低

Q:目前我们使用的是静态代理,这个静态代理的缺点是什么?

A:类爆炸。假设系统中有1000个接口,那么每个接口都需要对应代理类,这样类会急剧膨胀。不好维护。

Q:怎么解决类爆炸问题?

A:可以使用动态代理来解决这个问题。

动态代理还是代理模式,只不过添加了字节码生成技术,可以在内存中为我们动态的生成一个class字节码,这个字节码就是代理类。在内存中动态的生成字节码代理类的技术,叫做:动态代理。

动态代理

动态代理:在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

在内存当中动态生成类的技术常见的包括:

  • JDK动态代理技术:java.lang.reflect.Proxy,只能代理接口,即只适合有一个目标类和一个公共接口的情况
  • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)由于动态生成的类是在内存中生成的,可以采用继承的方式,无所谓其耦合度
  • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
JDK 动态代理技术
  • JDK 动态代理只能代理接口
  • 还是使用静态代理中的例子:一个接口和一个实现类。
公共接口
package cw.study.spring.service;/*** ClassName: OrderService* Package: cw.study.spring.service* Description:*/
public interface OrderService {String getName();/*** 生成订单*/void generate();/*** 修改订单*/void modify();/*** 查看订单详情*/void detail();}
目标对象
package cw.study.spring.service;/*** ClassName: OrderSeriveImpl* Package: cw.study.spring.service* Description:*/
public class OrderSeriveImpl implements OrderService {@Overridepublic String getName() {return "张三";}@Overridepublic void generate() {try {Thread.sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单生成成功...");}@Overridepublic void modify() {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单修改成功...");}@Overridepublic void detail() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单查询成功...");}
}
动态生成代理类分析
  • 在动态代理中代理类是可以动态生成的,这个类不需要写,我们直接写客户端程序即可
  • 需要理解:Object proxyObj = Proxy.newProxyInstance(类加载器, 代理类要实现的接口(公共接口), 调用处理器)
    • newProxyInstance:创建代理对象,调用该方法可以创建代理对象
      • Proxy.newProxyInstance():该方法的执行在内存中生成了代理类的字节码,并且通过内存中生成的代理类的字节码创建了代理对象
    • Proxy.newProxyInstance() 的三个参数:
      • 类加载器 ClassLoader loader:内存中动态生成的类的字节码需要加载到JVM中,需要类加载器,JDK要求代理类的类加载器和目标类的类加载器要是同一个
      • 代理类要实现的接口 Class<?>[] interfaces:代理类要和目标类实现相同的接口,代理类需要实现的接口需要我们进行告知
      • 调用处理器 InvocationHandler h:JDK不可能知道我们要代理类增强目标类哪些功能,JDK不知道增强的代码,这个需要我们进行传递,我们可以通过调用处理器告诉JDK代理类的增强代码,调用处理器 InvocationHandler 是一个接口,需要我们进行实现,在需要实现的方法中编写增强代码
实现调用处理器接口 InvocationHandler 编写增强代码
  • 编写增强代码的调用处理器只需要编写一次即可
  • 实现调用处理器接口 InvocationHandler,需要实现调用处理器的invoke方法,实现该方法其实就是在实现代理类实现公共接口时需要实现公共接口的方法的方法体
  • 增强代码在调用处理器接口 InvocationHandler 的invoke方法中编写
  • invoke方法由JDK在底层进行调用,如何调用invoke方法,JDK在底层已经写好了,当代理对象调用代理方法的时候,invoke方法会被调用
package client.cw.study.spring.improve;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;/*** ClassName: TimerInvocationHandler* Package: client.cw.study.spring.improve* Description:* 负责计时的调用处理器类* 在该类中编写关于计时的增强代码*/
public class TimerInvocationHandler implements InvocationHandler {// 目标对象private Object target;/*** 调用目标对象的目标方法是通过反射进行调用的,* 所以需要目标对象的引用** @param target 目标对象*/public TimerInvocationHandler(Object target) {this.target = target;}/*** 在invoke方法中写增强代码。* 调用代理对象的代理方法,对目标方法进行增强时要保证目标方法执行。* invoke方法是JDK进行调用的,JDK在调用该方法时,会将invoke方法需要的参数* 传递过来** @param proxy 代理对象的引用(使用较少)* @param method 目标对象的目标方法* @param args 目标方法上的实参* @return 目标对象的目标方法的返回值* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 调用目标对象的目标方法的前后编写增强代码// 目标方法执行开始时间long start = System.currentTimeMillis();// 调用目标对象的目标方法,那么这里需要一个目标对象:使用构造器传参Object returnVal = method.invoke(target, args);// 目标方法执行结束时间long end = System.currentTimeMillis();// 计算输出目标方法的执行耗时System.out.println(end - start);// 返回目标对象的目标方法的返回值return returnVal;}
}
客户端程序
package client;import client.cw.study.spring.improve.TimerInvocationHandler;
import cw.study.spring.service.OrderSeriveImpl;
import cw.study.spring.service.OrderService;
import cw.study.spring.service.OrderServiceProxy;import java.lang.reflect.Proxy;/*** ClassName: Client* Package: client* Description:** @Author tcw* @Create 2023-05-28 9:38* @Version 1.0*/
public class Client {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderSeriveImpl();// 创建代理对象// Object proxyObj = Proxy.newProxyInstance(类加载器, 代理类要实现的接口(公共接口), 调用处理器)OrderService orderService = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target));// 使用代理对象的代理方法orderService.generate();orderService.modify();orderService.detail();String name = orderService.getName();System.out.println(name);}
}

JDK 动态代理工具类封装
package client.cw.study.spring.improve;import cw.study.spring.service.OrderService;import java.lang.reflect.Proxy;/*** ClassName: ProxyUtil* Package: client.cw.study.spring.improve* Description:*/
public class ProxyUtil {private ProxyUtil() {}/*** 创建目标对象的代理对象** @param target 目标对象* @return 代理对象*/public static Object newProxyInstance(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 获取目标对象的目标类的类加载器target.getClass().getInterfaces(), // 获取目标对象的目标类实现的接口new TimerInvocationHandler(target) // 调用处理器);}
}
public class Client {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderSeriveImpl();// 创建代理对象// Object proxyObj = Proxy.newProxyInstance(类加载器, 代理类要实现的接口(公共接口), 调用处理器)// OrderService orderService = (OrderService) Proxy.newProxyInstance(//         target.getClass().getClassLoader(),//         target.getClass().getInterfaces(),//         new TimerInvocationHandler(target)// );OrderService orderService = (OrderService) ProxyUtil.newProxyInstance(target);// 使用代理对象的代理方法orderService.generate();orderService.modify();orderService.detail();String name = orderService.getName();System.out.println(name);}
}

CGLIB 动态代理
  • CGLIB 动态代理可以代理接口,也可以代理类
  • CGLIB 动态代理底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。

功能更强大,效率更高

CGLIB 依赖
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
目标类
package cw.study.spring.service;/*** ClassName: UserService* Package: cw.study.spring.service* Description:* 目标类*/
public class UserService {public boolean login(String username, String password) {System.out.println("正在验证身份...");if ("admin".equals(username) && "123".equals(password)) return true;return false;}public void logout() {System.out.println("退出登录...");}}
方法拦截器 MethodInterceptor

相当于要执行目标方法的时候,会被拦截器拦截,在执行增强代码的过程中执行目标方法,实现对目标方法的增强

package client.cw.study.spring.improve;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** ClassName: TimerMethodInterceptor* Package: client.cw.study.spring.improve* Description:*/
public class TimerMethodInterceptor implements MethodInterceptor {/*** 在该方法中编写对目标方法的增强代码* * @param target 目标对象* @param method 目标方法* @param objects 目标方法调用时的实参* @param methodProxy 代理方法* @return 目标方法的返回值* @throws Throwable*/@Overridepublic Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 调用目标前后编写增强代码long start = System.currentTimeMillis();// 调用目标对象的方法// 调用代理对象的父类的方法Object returnVal = methodProxy.invokeSuper(target, objects);long end = System.currentTimeMillis();System.out.println("耗时:" + (end - start));// 返回目标方法的返回值return returnVal;}
}
客户端
package client;import client.cw.study.spring.improve.TimerMethodInterceptor;
import cw.study.spring.service.UserService;
import net.sf.cglib.proxy.Enhancer;/*** ClassName: Client* Package: client* Description:*/
public class Client {public static void main(String[] args) {// 创建字节码增强器对象,CGLIB的核心对象,用于代理类的生成Enhancer enhancer = new Enhancer();// 告诉CGLIB代理的目标类,由于CGLIB采用的是继承的方式,所以目标类为代理类的父类enhancer.setSuperclass(UserService.class);// 设置回调,等同于JDK动态代理的调用处理器// 在CGLIB中需要实现的接口为MethodInterceptor方法拦截器(不是JDK动态代理中的InvocationHandler接口)enhancer.setCallback(new TimerMethodInterceptor());// 创建代理对象// 会在内存中生成目标类的子类,即代理类,然后会创建代理类的对象UserService userServiceProxy = (UserService) enhancer.create();// 使用代理对象的代理方法System.out.println(userServiceProxy.login("admin", "123") ? "登录成功" : "登录失败");userServiceProxy.logout();}
}
  • 对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:

--add-opens java.base/java.lang=ALL-UNNAMED--add-opens java.base/sun.net.util=ALL-UNNAMED

CGLIB 代理类命名格式
cw.study.spring.service.UserService$$EnhancerByCGLIB$$d609db49@2d6a9952class UserService$$EnhancerByCGLIB$$d609db49 extends UserService {}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/415869.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL复习3

视图 视图&#xff08;view&#xff09;是一种虚拟存在的表&#xff0c;是一个逻辑表&#xff0c;本省没有数据&#xff0c;内容由查询定义。 基表&#xff1a;用来创建视图的表叫做基表 通过视图&#xff0c;我们可以查看基表的部分数据。视图数据来自定义视图的查询中使用…

RISC-V (八)定时器中断

​​​​​​​riscv中断的分类 Core local INTerrupt: CLINT CLINT编程接口-寄存器 mtime寄存器&#xff0c;由中断触发的时钟&#xff0c;按照固定频率计数。

【基础算法总结】BFS_多源最短路问题

目录 1. 算法介绍2. 算法原理和代码实现542.01矩阵1020.飞地的数量1765.地图中的最高点1162.地图分析 3. 算法总结 1. 算法介绍 所谓多源&#xff0c;就是有多个起点。对应上一篇文章【BFS_最短路问题】的单源问题。这篇文章介绍用bfs解决边权为1(或边权相等)的多源最短路问题…

监控平台之rollup打包

设计思路 1.根据模块&#xff0c;通过index.js去调用执行调用 2.WebEyeSDK.js暴露方法&#xff0c;同时定义init方法&#xff0c;去初始化config里的上报参数 3.rollup/build里入口文件为WebEyeSDK.js进行打包 4.打包编译用babel&#xff0c;同时安装babel/preset-env智能预…

网络安全服务基础Windows--第12节-域与活动目录

工作组 在Windows环境中配置⼯作组相对简单&#xff0c;适合⼩型⽹络环境&#xff0c;如家庭或⼩型办公室⽹络。⼯作组通过简单的⽹络共享和本地管理来实现资源共享&#xff0c;⽽不依赖于中央控制的服务器。 ● 定义&#xff1a;⼯作组是⼀种对等⽹络模型&#xff0c;在这种…

【鸿蒙开发从0到1 day05】

一. 清除浮动 1.当外面的大盒子,仅仅只设置了宽度,里面的子盒子为了行排序, 设置了浮动,以至于小盒子脱标,大盒子的高度为0,这个时候就会导致大盒子下面的盒子会跑上去 解决办法方法一:给父盒子添加overflow:hidden,这个就是如果子盒子有溢出,,溢出部分会隐藏方法二:在子盒子的…

Linux【2】文件目录-ls进阶

目录 ls 组合使用&#xff1a;ls -lha​编辑 ls 通配符 ls .是隐藏文件 ls -a可以显示所有文件包括隐藏文件 ls- l列表形式&#xff0c;详细信息 ls -l -h 大小更详细 组合使用&#xff1a;ls -lha ls 通配符 *任意长度 &#xff1f;一个字符 带扩展名 可选from…

计算机网络-VRRP切换与回切过程

前面我们学习了VRRP选举机制&#xff0c;根据VRRP优先级与IP地址确定主设备与备份设备&#xff0c;这里继续进行主备切换与主备回切以及VRRP抢占模式的学习。 一、VRRP主备切换 主备选举时根据优先级选择主设备&#xff0c;状态切换为Master状态&#xff0c;那当什么时候会切换…

HTTPS 协议“加密和解密”详细介绍

目录 一、加密 二、HTTPS的工作过程 2.1 引入对称加密 2.2 引入非对称加密 2.3 中间人攻击 2.4 引入证书 2.5 理解数据签名 2.6 通过证书解决中间人攻击 三、总结 HTTPS 是一个应用层协议&#xff0c;是在 HTTP 协议的基础上引入了一个加密层。 一、加密 加密就是把明文&#x…

Golang环境安装、配置详细

Windows下安装Go开发环境 点我下载 Windows配置Go环境变量 出现工具install失败时&#xff0c;切换其它代理 # 1. 七牛 CDN go env -w GOPROXYhttps://goproxy.cn,direct# 2. 阿里云 go env -w GOPROXYhttps://mirrors.aliyun.com/goproxy/,direct# 3. 官方 go env -w GOP…

【wsl2】从C盘迁移到G盘

参考大神 C盘的ubuntu22.04 非常大&#xff0c;高达30g 迁移后就只有几百M了&#xff1a; 右键有一个move没有敢尝试 迁移过程 Windows PowerShell Copyright (C) Microsoft Corporation. All rights reserved.Install the latest PowerShell for new features and improveme…

Xcode插件开发

Xcode插件开发 文章目录 Xcode插件开发一、插件开发流程创建插件Extension文件介绍文件说明 二、插件使用安装说明 一、插件开发流程 创建插件的过程并不复杂&#xff0c;只是官方教程&#xff0c;过于简单&#xff0c;所以这里补充下创建细节 创建插件 环境&#xff1a;Xco…

vue在生产环境和测试环境去掉 console 打印日志 只保留 “error“、 “warn“

vue在生产环境和测试环境去掉 console 打印日志 只保留 “error”、 “warn” 文章目录 vue在生产环境和测试环境去掉 console 打印日志 只保留 "error"、 "warn"一、安装插件二、babel.config.js配置 一、安装插件 npm install babel-plugin-transform-r…

C++11中的function和bind

目录 1.一个引例 2.function 什么是function&#xff1f; function模板原型 function的使用 使用示例代码 使用function解决引例中的问题 3.bind 什么是bind&#xff1f; 如何理解bind&#xff1f; bind的使用 4.function和bind总结 1.一个引例 看下面这一段代码…

仿华为车机UI--图标从Workspace拖动到Hotseat同时保留图标在原来位置

基于Android13 Launcher3,原生系统如果把图标从Workspace拖动到Hotseat里则Workspace就没有了&#xff0c;需求是执行拖拽动作后&#xff0c;图标同时保留在原位置。 实现效果如下&#xff1a; 实现思路&#xff1a; 1.如果在workspace中拖动&#xff0c;则保留原来“改变图标…

前端脚手架,自动创建远程仓库并推送

包含命令行选择和输入配置&#xff0c;远程仓库拉取模板&#xff0c;根据配置将代码注入模板框架的代码中&#xff0c;自动创建远程仓库&#xff0c;初始化git并提交至远程仓库&#xff0c;方便项目开发&#xff0c;简化流程。 目录结构 创建一个bin文件夹&#xff0c;添加ind…

云计算之存储

目录 一、产品介绍 1.1 对象存储oss 1.2 特点 二、产品技术背景 三、产品架构及功能 四、常见问题及排查思路 4.1 两个bucket目录文件如何快速复制&#xff1f; 4.2 oss里的目录如何删除&#xff1f; 4.3 能否统计oss一个目录的大小 4.4 异常诊断 - 上传下载速度慢 4…

CentOS 7安装Docker详细步骤-无坑-丝滑-顺畅

一&#xff0c;安装软件包 yum install -y yum-utils device-mapper-persistent-data lvm2二&#xff0c;更换yum源为阿里源&#xff1a; yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 三&#xff0c;查看docker版本&…

uniapp 自定义微信小程序 tabBar 导航栏

背景 做了一个校园招聘类小程序&#xff0c;使用 uniapp vue3 uview-plus pinia 构建&#xff0c;这个小程序要实现多角色登录&#xff0c;根据权限动态切换 tab 栏文字、图标。 使用pages.json中配置tabBar无法根据角色动态配置 tabBar&#xff0c;因此自定义tabBar&…

交换机自动化备份配置(H3C_无人值守)

介绍&#xff1a; 在日常运维过程中&#xff0c;需要定时备份设备的配置&#xff0c;在设备数量过于庞大的情况下&#xff0c;对我们的运维工作会造成极大地不便&#xff0c;通过python自动化能够完美解决人工手动保存设备配置的问题。而且自动化运维在未来也一定是大势所趋&a…