spring揭秘07-aop01-aop基本要素及代理模式3种实现

news/2024/9/20 3:54:02/ 标签: spring, 代理模式, java

文章目录

  • 【README】
  • 【1】AOP思想演进
    • 【1.1】AOL:面向切面语言
    • 【1.2】AOP实现设想
    • 【1.3】java平台的AOP实现机制
      • 【1.3.1】动态代理(需要目标类实现接口)
      • 【1.3.2】动态字节码增强(不需要目标类实现接口)
      • 【1.3.3】自定义加载器
  • 【2】AOP基本要素
    • 【2.1】Joinpoint切点
    • 【2.2】Pointcut切点表达式
    • 【2.3】Advice通知(横切逻辑)
    • 【2.4】Aspect切面
    • 【2.5】织入器
    • 【2.6】目标对象
  • 【3】代理模式

【README】

本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;

1)业务场景: 需要为每个访问数据库的dao方法新增统计sql执行耗时逻辑; 如何实现? 每个dao方法都调用统计耗时的公共逻辑? 会不会太复杂? 我想,一个正常的技术架构不应该是这样的。 此外,公共逻辑包括但不限于方法执行监控,访问日志收集,数据脱敏等,这些公共逻辑都涉及到如何注入到正常业务逻辑的问题;

  • 解决方法: 把统计sql执行耗时公共逻辑通过代理模式织入到dao方法被调用的上文和下文;
  • 如何实现代理模式? 通过静态代理,还是JDK动态代理,亦或是CGLIB字节码增强动态代理? 对的,这就是引入AOP的目的,即通过代理模式把公共逻辑织入到正常业务功能的上下文,一定程度上可以做到业务代码无侵入织入,实现公共逻辑代码高内聚而不是散落在各个业务方法中

2)AOP: Aspect-Oriented Programming,面向切面编程; AOP中,公共逻辑被抽取为切面, 用户代码通过硬编码或配置或注解把切面逻辑织入到业务功能的上文或下文或者上下文;


【1】AOP思想演进

【1.1】AOL:面向切面语言

与OOP类似,AOP也是一种软件架构概念,它也需要编程语言来实现;

1)AOL面向切面语言:实现AOP的编程语言;AOL语言类型不需要与应用系统编程语言相同;(如应用系统使用java,AOL可以选择AspectC,理论上而言)

2)AOL清单:

  • AspectJ
  • AspectC
  • AspectC++

3)切面织入:把面向切面组件集成(注入)到OOP组件的过程;或者把切面逻辑织入到业务逻辑上下文的过程;

【1.2】AOP实现设想

1)静态AOP: 以最初的AspectJ为代表;底层原理是: 通过特定编译器,把切面逻辑编译成字节码,并把字节码织入到业务class文件形成新的class字节码文件(理解为编译时织入);

  • 优点:没有任何性能损失;
  • 缺点:灵活性不够; 因为有任何变动,都需要修改切面逻辑,重新编译其字节码,重新织入;

2)动态AOP: 在系统运行之后织入切面到切点,而不是在编译时;而且织入信息采用XML文件或其他配置文件保存,运维成本低;若有修改,则修改配置即可;

  • 缺点:有性能损失; 因为动态AOP是在系统运行期间织入切面,会有一定的性能损失(理解为运行时织入 );

javaAOP_49">【1.3】java平台的AOP实现机制

【1.3.1】动态代理(需要目标类实现接口)

1)动态代理:底层使用JDK动态代理 api,把切面逻辑封装到动态代理的InvocationHandler接口实现类中;

  • 缺点1: 所有需要织入切面逻辑的模块类都需要实现对应接口; 因为动态代理机制仅针对接口有效
  • 缺点2:动态代理在运行时使用反射的运行时织入,相对于编译时织入,有性能损失;
  • 补充:Spring AOP 默认使用JDK动态代理实现AOP

【1.3.2】动态字节码增强(不需要目标类实现接口)

1)动态字节码增强(生成)技术: 使用ASM或CGLIB等java工具库,在程序运行期间为目标对象生成子类,修改子类class文件字节码文件(如新增切面逻辑字节码), 只要修改后的子类class文件满足jvm规范,其就可以正常运行

2)基于动态字节码增强的AOP: 通过动态字节码增强技术针对被织入的类(目标对象类)生成相应子类,并把切面逻辑字节码添加到子类,应用系统在执行期间使用子类bean(代理bean)处理业务逻辑;

3)当目标类没有实现接口, spring 使用动态字节码增强实现AOP

【1.3.3】自定义加载器

1)自定义加载器: 加载器通过读取外部文件规定的织入规则和必要信息,在加载class文件时就把切面逻辑织入到class文件,然后把改动后的class文件交给jvm运行;

  • AspectJ项目的AspectWerkz框架采用的是自定义类加载器实现AOP;
  • 缺点: 某些应用服务器会控制整个类加载器,用户可能无法自定义类加载器的情况;

【2】AOP基本要素

【2.1】Joinpoint切点

1)Joinpoint切点:被织入切面逻辑的程序位置(执行点);

2)切点类型:

  • 方法调用: 当前方法被外部组件调用的程序位置;如构造方法调用;
  • 方法调用执行:当期方法内部执行开始位置到结束位置;
    • 包括构造方法执行,字段设置, 字段获取, 异常处理执行,类初始化(静态代码块初始化);

【2.2】Pointcut切点表达式

1)Pointcut切点表达式: 表示切点位置的表达式;

2)切点表达式类型:

  • 直接指定切点所在方法名称;
  • 正则表达式;
  • 使用特定的Pointcut表述语言;

【2.3】Advice通知(横切逻辑)

1)Advice通知:被织入到切点的横切逻辑(简单理解: 横切逻辑就是切面逻辑,本文认为横切这个术语更加符合aop语义 );

2)横切逻辑类型:

  • Before Advice: 在切点位置之前执行;
  • After Advice: 在切点位置之后执行;
    • After returning Advice: 当切点位置的程序正常执行完成后,才被执行(正常返回);
    • After throwing Advice: 当切点位置的程序抛出异常时,才被执行(抛出异常,非正常返回);
    • After Advice(After Finally Advice): 当切点位置的程序执行正常或异常,都被执行;
  • Around Advice:环绕通知; 在切点位置之前或者之后执行;
  • Introduction:引入通知; 这个非常重要;

3)非引入通知与引入通知区别:

  • 非引入通知:包括Before,After,Around等通知; 以上类型通知是把目标对象(被织入横切逻辑的对象)已有方法作为锚点(载体)织入横切逻辑;(简单理解:纵向织入; 织入横切逻辑到已有方法上下文,织入动作影响已有方法逻辑
  • 引入通知:仅 Introduction,顾名思义,引入的意思是为目标对象织入新方法,它不会把目标对象方法作为锚点 ;(简单理解:横向织入;织入新方法,织入动作不影响已有方法

【2.4】Aspect切面

1)Aspect切面:封装多个Pointcut切点表达式与Advice切面逻辑的实体;

【2.5】织入器

1)spring aop织入器:ProxyFactory类是spring aop最通用的织入器;

2)织入器职责: 把切面逻辑织入到切点;

【2.6】目标对象

1)目标对象: 被织入横切逻辑的对象;



【3】代理模式

1)spring aop底层使用JDK动态代理或动态字节码增强技术把横切逻辑织入到目标对象

2)代理模式有3种实现方式:

  • 静态代理;
  • JDK动态代理;
  • CGLIB动态代理(动态字节码增强(生成)技术)

【3.1】静态代理模式

1)静态代理模式: 需要目标类与代理类都实现相同接口;

【StaticProxyMain】

java">public class StaticProxyMain {public static void main(String[] args) {new StaticProxyMsgSenderImpl(new YidongMsgSenderImpl()).send("您好,您有待办事项需要处理", "123456");}
}

【StaticProxyMsgSenderImpl】静态代理类 ( 实现接口

java">public class StaticProxyMsgSenderImpl implements IMsgSender {private IMsgSender msgSendSupport;public StaticProxyMsgSenderImpl(IMsgSender msgSendSupport) {this.msgSendSupport = msgSendSupport;}@Overridepublic void send(String msg, String phoneNum) {System.out.println("static proxy busi: before");msgSendSupport.send(msg, phoneNum);System.out.println("static proxy busi: after");}
}

【IMsgSender】接口

java">public interface IMsgSender {void send(String msg, String phoneNum);default boolean checkAuth(String phoneNum) {System.out.printf("IMsgSender#checkAuth(): 校验权限; 电话号码:[%s]\n", phoneNum);return false;}
}

【YidongMsgSenderImpl】接口实现类 ( 实现接口

java">public class YidongMsgSenderImpl implements IMsgSender {@Overridepublic void send(String msg, String phoneNum) {System.out.printf("运营商:[中国移动],短信内容:[%s] ; 电话号码:[%s] \n", msg, phoneNum);}
}

【打印日志】

static proxy busi: before
运营商:[中国移动],短信内容:[您好,您有待办事项需要处理] ; 电话号码:[123456] 
static proxy busi: after

【3.1.1】静态代理模式的问题

1)业务场景: 应用系统不仅有短信发送器(IMsgSender),还有邮件发送器(IEmailSender),两个发送器的发送方法都是send(),且系统监控需求都需要对两个send() 方法做监控; 按照静态代理思想, 岂不是还要为邮件发送器新建一个邮件发送器实现类? 如果还有微信发送器(IWechatSender),那还要再新建一个微信发送器实现类? 累不累?

  • 这就是静态代理模式的局限所在了, 要求每个目标对象的代理对象都要实现对应接口,即便拦截的方法名相同;显然,静态代理模式就不适合做aop ,因为通用性不够

【3.2】JDK动态代理模式

1)基于JDK动态代理API实现动态代理: 对于方法名相同而目标对象不同的代理,仅需要提供1个InvocationHandler接口的实现类;

【JdkDynamicProxyMain】JDK动态代理测试入口main

java">public class JdkDynamicProxyMain {public static void main(String[] args) {// 发送短信动态代理IMsgSender msgSenderTarget = new YidongMsgSenderImpl();IMsgSender dynamicProxyMsgSender = (IMsgSender) Proxy.newProxyInstance(msgSenderTarget.getClass().getClassLoader(), new Class[]{IMsgSender.class}, new SenderInvocationHandler(msgSenderTarget));String message = "您好,您有待办任务需要处理";String phoneNum = "123456";dynamicProxyMsgSender.checkAuth(phoneNum);dynamicProxyMsgSender.send(message, phoneNum);System.out.println("\n我是分割线=================\n");// 发送邮箱动态代理IEmailSender emailSenderTarget = new GoogleEmailSenderImpl();IEmailSender dynamicProxyEmailSender = (IEmailSender) Proxy.newProxyInstance(emailSenderTarget.getClass().getClassLoader(), new Class[]{IEmailSender.class}, new SenderInvocationHandler(emailSenderTarget));dynamicProxyEmailSender.checkAuth("123@gamil.com");dynamicProxyEmailSender.send(message, "123@gamil.com");}
}

【SenderInvocationHandler】InvocationHandler接口实现类

java">public class SenderInvocationHandler implements InvocationHandler {private Object target;public SenderInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = null;if ("send".equals(method.getName())) {System.out.println("send() execute : before");result = method.invoke(target, args);System.out.println("send() execute : after");} else {result = method.invoke(target, args);}return result;}
}

【IEmailSender】邮件发送器接口

java">public interface IEmailSender {void send(String content, String addr);default boolean checkAuth(String addr) {System.out.printf("IEmailSender#checkAuth(): 校验权限; 邮箱地址:[%s]\n", addr);return false;}
}

【GoogleEmailSenderImpl】邮件发送器接口实现类

java">public class GoogleEmailSenderImpl implements IEmailSender {@Overridepublic void send(String content, String addr) {System.out.printf("发送邮件: 内容=[%s], 邮箱地址=[%s]\n", content, addr);}
}

【打印日志】

IMsgSender#checkAuth(): 校验权限; 电话号码:[123456]
send() execute : before // 横切逻辑上文
运营商:[中国移动],短信内容:[您好,您有待办任务需要处理] ; 电话号码:[123456] 
send() execute : after  // 横切逻辑下文我是分割线=================IEmailSender#checkAuth(): 校验权限; 邮箱地址:[123@gamil.com]
send() execute : before // 横切逻辑上文上文
发送邮件: 内容=[您好,您有待办任务需要处理], 邮箱地址=[123@gamil.com]
send() execute : after  // 横切逻辑下文 

【3.2.1】JDK动态代理模式的问题

1)总结: JDK动态代理解决了静态代理模式中每个目标对象的代理对象都要实现对应接口,即便拦截的方法名相同的问题;

2)JDK动态代理局限性: 所有目标对象(被织入切面逻辑的对象)都需要实现对应接口; 因为动态代理机制仅针对接口有效(即,若目标对象没有实现接口,则无法使用JDK动态代理)

  • 如 YidongMsgSenderImpl 实现IMsgSender接口, GoogleEmailSenderImpl 实现 IEmailSender接口 ;

3)问题:因为JDK动态代理仅针对接口有效,则当应用系统集成的第三方类库存在没有实现接口的类,则这些类就无法通过JDK动态代理实现AOP, 显然,这是不合理的;

  • 解决方法: 使用动态字节码增强技术实现动态代理,即便目标对象或目标类没有实现接口,也可以实现动态代理;

java.lang.reflect.Proxy】JDK中newProxyInstance定义:

java">/**
* Params:
loader – the class loader to define the proxy class 
interfaces – the list of interfaces for the proxy class to implement 代理类需要实现的接口列表 
h – the invocation handler to dispatch method invocations to ()  
*/
@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {

4)若目标对象不实现接口,则无法使用 JDK动态代理,如下。

【NonInterfaceJdkDynamicProxyMain】目标对象不实现接口导致无法实现JDK动态代理测试main

传入的参数是实现类class数组:new Class[]{GoogleEmailSenderImpl.class} , 而不是接口class数组:new Class[]{IEmailSender.class}

java">public class NonInterfaceJdkDynamicProxyMain {public static void main(String[] args) {// 发送邮箱动态代理GoogleEmailSenderImpl emailSenderTarget = new GoogleEmailSenderImpl();GoogleEmailSenderImpl dynamicProxyEmailSender = (GoogleEmailSenderImpl) Proxy.newProxyInstance(emailSenderTarget.getClass().getClassLoader(), new Class[]{GoogleEmailSenderImpl.class}, new SenderInvocationHandler(emailSenderTarget));dynamicProxyEmailSender.checkAuth("123@gamil.com");dynamicProxyEmailSender.send("您好,您有待办任务需要处理", "123@gamil.com");}
}

【报错】

Exception in thread "main" java.lang.IllegalArgumentException: com.tom.springnote.chapter08proxypattern.GoogleEmailSenderImpl is not an interface


【3.3】动态字节码增强(生成)动态代理

1)动态字节码增强(生成)技术: 使用ASM或CGLIB等java工具库,在程序运行期间为目标对象生成子类,修改子类class字节码文件(如新增切面逻辑字节码), 只要修改后的子类class文件满足jvm规范,其就可以正常运行

2)基于动态字节码增强的动态代理: 通过动态字节码增强技术针对目标类生成子类,子类通过覆写扩展父类(目标对象类)方法,即把切面逻辑字节码添加到子类(代理类),应用系统运行时使用子类bean(代理类bean)处理业务逻辑;

【CglibDynamicProxyMain】CGLIB动态代理main

java">public class CglibDynamicProxyMain {public static void main(String[] args) {// 传入targetBusiTarget target = new BusiTarget();Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(new CglibMethodInterceptorImpl(target));BusiTarget dynamicByteProxyTaskSender = (BusiTarget) enhancer.create();dynamicByteProxyTaskSender.send1("您有1个待办任务需要处理", "task001");System.out.println();dynamicByteProxyTaskSender.send2("您有2个待办任务需要处理", "task002");}
}

【BusiTarget】目标类,目标对象所属类,被代理类 ( 目标类没有实现接口 ,也可以实现动态代理

java">public class BusiTarget {public void send1(String content, String taskId) { System.out.printf("BusiTarget#send1(): 发送任务,内容=[%s], taskId=[%s]\n", content, taskId);}public void send2(String content, String taskId) {System.out.printf("BusiTarget#send2(): 发送任务,内容=[%s], taskId=[%s]\n", content, taskId);}
}

【CglibMethodInterceptorImpl】CGLIB方法拦截器实现类

java">public class CglibMethodInterceptorImpl implements MethodInterceptor {private final Object target;public CglibMethodInterceptorImpl(Object target) {this.target = target;}@Overridepublic Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("send() execute : before");Object result = method.invoke(target, args);System.out.println("send() execute : after");return result;}
}

【打印日志】

send() execute : before
BusiTarget#send1(): 发送任务,内容=[您有1个待办任务需要处理], taskId=[task001]
send() execute : aftersend() execute : before
BusiTarget#send2(): 发送任务,内容=[您有2个待办任务需要处理], taskId=[task002]
send() execute : after

【3.4】3种代理模式实现小结

1)静态代理模式:通过硬编码方式,传入目标对象构建代理对象; 不灵活,对于每个目标对象(类)都需要新建一个代理类;

2)JDK动态代理模式:针对接口实现代理,目标对象需要实现接口,而代理对象需要实现InvocationHandler接口;与静态代理不同的是,如果多个目标对象的被拦截方法名相同,则仅需要新建一个InvocationHandler实现类; (通过实现接口实现动态代理

3)动态字节码增强动态代理: 动态字节码增强技术针对目标类生成一个子类,子类(代理类)通过覆写扩展父类(目标对象类)方法功能,把子类对象作为代理对象返回给应用系统使用;( 通过继承父类实现动态代理




http://www.ppmy.cn/news/1515896.html

相关文章

vue使用vite配置跨域以及环境配置详解

vue前后端分离开发&#xff0c;配置代理都是绕不开的话题&#xff0c;下面是vite的代理 server: {proxy: {/api: {target: https://api.hello.com, //代理的服务地址&#xff0c;可以理解为/api等于https://api.hello.comsecure: true, // 配置httpschangeOrigin: true, //…

工厂现场多功能帮手,三防平板改善管理体验

随着制造业的智能化变革&#xff0c;信息化、自动化和智能化逐渐成为工厂管理的新常态。在这一波技术浪潮中&#xff0c;三防平板作为一种多功能的工作工具&#xff0c;正在逐步改善工厂现场的管理体验。 一、三防平板的定义与特点 三防平板&#xff0c;顾名思义&#xff0c;是…

Python中的偏函数及其广泛应用方式例子解析

代码示例&#xff1a; 导入Python内置的functools模块 import functools定义一个简单的装饰器&#xff0c;用于打印函数执行前的信息 def print_before(func):functools.wraps(func)def wrapper(*args, **kwargs):print(f"Calling {func.__name__!r} with:")print…

openai whisper使用

whisper使用 介绍 Whisper是一种通用的语音识别模型。它是在大量不同音频数据集上训练的&#xff0c;也是一个多任务模型&#xff0c;可以执行多语言语音识别、语音翻译和语言识别。 GitHub&#xff1a;https://github.com/openai/whisper 论文链接&#xff1a;https://arx…

游戏开发中客户端和服务器逻辑

文章目录 0 引言1 服务器逻辑1.1 游戏逻辑的权威性1.2 状态同步1.3 安全性和反作弊1.4 AI控制 2.客户端职责2.1 用户输入处理2.2 渲染和表现2.3 本地预测和插值2.4 UI和HUD 0 引言 在进行游戏开发的过程中&#xff0c;对于客户端和服务器端要进行的逻辑有些困惑&#xff0c;学…

如何给两台机器集群设置VIP(虚拟IP)

原文链接&#xff1a;https://www.cnblogs.com/qianz/articles/16825567.html 一、环境准备 1.集群 IP部署 172.16.30.181是主节点&#xff0c;172.16.30.182是备节点&#xff0c;VIP是172.16.30.183 我是在openstack上搭建的集群&#xff0c;建议大家将需要用到的IP都固定到…

LeetCode --- 411周赛

题目列表 3258. 统计满足 K 约束的子字符串数量 I 3259. 超级饮料的最大强化能量 3260. 找出最大的 N 位 K 回文数 3261. 统计满足 K 约束的子字符串数量 II 一、统计满足K约束的子字符串数量I 这种要求满足区间内某种性质的题&#xff0c;一般都可以用滑动窗口来做。这题…

C#高级进阶---关于插件开发(初版)

一、关于插件 插件开发是一种使应用程序功能可扩展的技术。通过插件&#xff0c;应用程序可以动态地加载和使用外部功能模块&#xff0c;而无需重新编译整个程序。 1. 插件架构设计 插件系统通常包含以下几个核心部分&#xff1a; 主程序&#xff08;Host Application&#x…

20240824给飞凌OK3588-C的核心板刷Ubuntu22.04并安装iperf3测试网速

20240824给飞凌OK3588-C的核心板刷Ubuntu22.04并安装iperf3测试网速 2024/8/24 9:24 缘起&#xff0c;通过千兆交换机接入外网&#xff0c;开机之后发现以太网异常&#xff0c;多上电几次就会发现以太网也能用。 缘由&#xff1a;由于自制的飞凌的OK3588-C的核心板的底板对空间…

Vue3-win7搭建vue3环境

Vue3-win7搭建vue3环境 官方要求的信息是是node.js 18.03以上。而我的环境&#xff1a;win7 x64&#xff0c; vscode 1.34。 参考网址&#xff1a; 0、基本的安装 https://blog.csdn.net/m0_49139268/article/details/126159171 a、这里有各种安装包的下载路径&#xff08;镜…

ES详细使用!Elasticsearch实现索引操作,增删改查,批处理

要想知道ES怎么具体实现对数据的操作&#xff0c;我们首先应该了解一下什么叫做restful编码风格&#xff0c;因为es的具体操作都是restful风格的。 1.RESTful风格 RESTful 是一种软件架构风格&#xff0c;用于创建可扩展的网络服务。它使用 HTTP 方法&#xff08;如 GET、POS…

【系统架构设计师-2018年】案例分析-答案及详解

试题一&#xff08;25分&#xff09; 阅读以下关于软件系统设计的叙述&#xff0c;在答题纸上回答问题1至问题3。 【说明】 某文化产业集团委托软件公司开发一套文化用品商城系统&#xff0c;业务涉及文化用品销售、定制、竞拍和点评等板块&#xff0c;以提升商城的信息化建设…

【ORACLE】 ORA-01691: Lob 段无法通过 8192 (在表空间 XXX_SPACE 中) 扩展

ORA-01691错误通常表示Oracle数据库在尝试扩展LOB段时无法为表空间分配更多的空间。这个问题通常由表空间容量不足引起。根据搜索结果&#xff0c;以下是几种可能的解决方案&#xff1a; 检查并扩大表空间&#xff1a;首先&#xff0c;确认表空间是否已经达到其最大容量。可以使…

Excel中的“块”操作

在Excel中&#xff0c;有offset、index、indirect三个对“区域”操作的函数&#xff0c;是较高版本Excel中“块”操作的利器。 (笔记模板由python脚本于2024年08月20日 19:25:21创建&#xff0c;本篇笔记适合喜欢用Excel处理数据的coder翻阅) 【学习的细节是欢悦的历程】 Pytho…

HarmonyOs应用权限申请,system_grant和user_grant区别。本文附头像上传申请user-grant权限代码示例

HarmonyOs应用权限申请&#xff0c;system_grant和user_grant区别。本文附头像上传申请user-grant权限代码示例 system_grant&#xff08;系统授权&#xff09; system_grant指的是系统授权类型&#xff0c;在该类型的权限许可下&#xff0c;应用被允许访问的数据不会涉及到用户…

状态压缩DP---最短Hamilton路径

给定一张 nn 个点的带权无向图&#xff0c;点从 0∼n−10∼n−1 标号&#xff0c;求起点 00 到终点 n−1n−1 的最短 Hamilton 路径。 Hamilton 路径的定义是从 00 到 n−1n−1 不重不漏地经过每个点恰好一次。 输入格式 第一行输入整数 nn。 接下来 nn 行每行 nn 个整数&a…

火绒使用详解 为什么选择火绒?使用了自定义规则及其高级功能的火绒,为什么能吊打卡巴斯基,360,瑞星,惠普联想戴尔的电脑管家等?

目录 前言 必看 为什么选择火绒&#xff1f; 使用了自定义规则及其高级功能的火绒&#xff0c;为什么能吊打卡巴斯基&#xff0c;360&#xff0c;瑞星&#xff0c;惠普联想戴尔的电脑管家等&#xff1f; 原因如下&#xff1a; 火绒的主要优势 1. 轻量化设计 2. 强大的自…

wsl2 airsim wairing for connect (Windows11 UE4.27)问题解决

一、概述 这里记述我遇到我在使用wsl2子系统与Windows11上进行交互时候&#xff0c;遇到的一些我之前没有遇到过的问题。 之前的我写的配置链接在这里。 UE5 with plugins AirSim in Windows & ROS in WSL2-Ubuntu 20.04配置过程记录_airsim ue5-CSDN博客文章浏览阅读455次…

python循环访问excel的某一列从某行开始的内容

python循环访问excel的某一列从某一行开始的内容 您可以使用openpyxl库来实现这个需求。以下是一个示例代码&#xff0c;展示了如何使用openpyxl读取Excel文件中的特定列&#xff08;假设为第一列&#xff0c;从第二行开始&#xff09;&#xff1a; 可以使用Python的openpyxl…

FastAPI+Vue3零基础开发ERP系统项目实战课 20240824上课笔记 循环和函数以及大量的练习

巩固一下 假设我们现在想要根据输入月份&#xff0c;判断是哪个季节&#xff0c;怎么做呀&#xff1f; 输入1&#xff1a;一月&#xff0c;12月&#xff0c;1月&#xff0c;2月是冬季 输入8&#xff1a;八月&#xff0c;夏季 作为思考题&#xff0c;有时间就做一下&#xff…