设计模式之【代理模式】,有事找我“经纪人”

news2024/11/27 11:38:21

文章目录

  • 一、什么是代理模式
    • 1、代理模式三大角色
    • 2、代理、桥接、装饰器、适配器 4 种设计模式的区别
    • 3、代理模式使用场景
    • 4、代理模式优缺点
  • 二、静态代理
    • 1、静态代理的一般写法
    • 2、火车站售票案例
    • 3、静态代理优缺点
  • 三、动态代理
    • 1、静态代理和动态代理的本质区别
    • 2、JDK动态代理
      • (1)火车票售票案例
      • (2)使用JDK动态代理手写简易MyBatis
      • (3)深入理解
    • 3、CGLIB动态代理
      • (1)火车票售票案例
      • (2)深入理解
    • 4、CGLIB和JDK动态代理对比
  • 四、源码中的代理
    • 1、SpringAOP
    • 2、MyBatis拦截器插件

一、什么是代理模式

代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制对这个对象的访问,属于结构型模式。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

1、代理模式三大角色

在这里插入图片描述
代理模式一般包含三种角色:

  • 抽象主题角色(Subject):抽象主题类的主要职责是声明真实主题与代理的共同接口方法,该类可以是接口也可以是抽象类;
  • 真实主题角色(RealSubject):该类也被称为被代理类,该类定义了代理所表示的真实对象,是负责执行系统真正的逻辑业务对象;
  • 代理主题角色(Proxy):也被称为代理类,其内部持有RealSubject的引用,因此具备完全的对RealSubject的代理权。客户端调用代理对象的方法,同时也调用被代理对象的方法,但是会在代理对象前后增加一些处理代码。

在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。代理模式属于结构型模式。

2、代理、桥接、装饰器、适配器 4 种设计模式的区别

代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。

尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。这里我就简单说一下它们之间的区别。

代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

3、代理模式使用场景

(1)远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

(2)防火墙(Firewall)代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

(3)保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

(4)缓存代理
当请求图片文件等资源时,先从缓存代理取,如果取不到资源再到公网或者数据库取。

(5)同步代理
主要使用在多线程编程中,完成多线程间同步工作。

4、代理模式优缺点

优点:

  • 代理模式能将代理对象与真实被调用目标对象分离。
  • 在一定程度上降低了系统的耦合性,扩展性好。
  • 可以起到保护目标对象的作用。
  • 可以增强目标对象的功能。

缺点:

  • 代理模式会造成系统设计中类的数量增加。
  • 在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢。
  • 增加了系统的复杂度。

二、静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。

1、静态代理的一般写法

// 抽象接口
public interface ISubject {
    void request();
}
// 真实对象
public class RealSubject implements ISubject {

    public void request() {
        System.out.println("real service is called.");
    }
}
// 代理类
public class Proxy implements ISubject {

    private ISubject subject;

    public Proxy(ISubject subject){
        this.subject = subject;
    }

    public void request() {
        before();
        subject.request();
        after();
    }

    public void before(){
        System.out.println("called before request().");
    }

    public void after(){
        System.out.println("called after request().");
    }
}

public static void main(String[] args) {

    Proxy proxy = new Proxy(new RealSubject());
    proxy.request();
}

2、火车站售票案例

如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。

//卖票接口
public interface SellTickets {
	void sell();
}
//火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {
	public void sell() {
		System.out.println("火车站卖票");
	}
}
//代售点
public class ProxyPoint implements SellTickets {
	private TrainStation station;
	public ProxyPoint(TrainStation station) {
		this.station = station;
	}
	public void sell() {
		System.out.println("代理点收取一些服务费用");
		station.sell();
	}
}
//测试类
public class Client {
	public static void main(String[] args) {
		ProxyPoint pp = new ProxyPoint(new TrainStation());
		pp.sell();
	}
}

3、静态代理优缺点

优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。
缺点:代理对象需要与目标对象实现相同的接口,所以会有很多代理类,一个目标对象需要对应一个代理类。一旦接口增加方法,目标对象与被代理对象都需要维护。

三、动态代理

1、静态代理和动态代理的本质区别

(1)静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违背开闭原则。
(2)动态代理采用在运行时动态生成代码的方法,取消了对被代理类的扩展机制,遵循开闭原则。
(3)若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只要新增策略类便可完成,无需修改代理类的代码。

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。

2、JDK动态代理

(1)火车票售票案例

//卖票接口
public interface SellTickets {
	void sell();
}
//火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {
	public void sell() {
		System.out.println("火车站卖票");
	}
}

//代理工厂,用来创建代理对象
public class ProxyFactory {
	private TrainStation station;
	public ProxyPoint(TrainStation station) {
		this.station = station;
	}
	public SellTickets getProxyObject() {
		//使用Proxy获取代理对象
		/*
			newProxyInstance()方法参数说明:
			ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
			Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
			InvocationHandler h : 代理对象的调用处理程序
		*/
		SellTickets sellTickets = (SellTickets)
		Proxy.newProxyInstance(station.getClass().getClassLoader(),
		station.getClass().getInterfaces(),
		new InvocationHandler() {
			/*
			InvocationHandler中invoke方法参数说明:
			proxy : 代理对象
			method : 对应于在代理对象上调用的接口方法的 Method 实
			例
			args : 代理对象调用接口方法时传递的实际参数
			*/
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
				//执行真实对象
				Object result = method.invoke(station, args);
				return result;
			}
		});
		return sellTickets;
	}
}

//测试类
public class Client {
	public static void main(String[] args) {
		//获取代理对象
		ProxyFactory factory = new ProxyFactory(new TrainStation());
		SellTickets proxyObject = factory.getProxyObject();
		proxyObject.sell();
	}
}

(2)使用JDK动态代理手写简易MyBatis

import org.apache.ibatis.annotations.Select;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

/**
 * 手写mybatis简单示例
 */
public class MybatisTest1 {


    public static void main(String[] args) {
        // JDK动态代理
        UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(MybatisTest1.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 获取接口方法上的@Select注解
                Select annotation = method.getAnnotation(Select.class);
                // 构造方法的参数名称与值的map
                Map<String, Object> methodArgsNameMap = buildMethodArgsNameMap(method, args);
                if(annotation != null){
                    // 获取接口上的sql,我们这只处理一个
                    String sql = parseSQL(annotation.value()[0], methodArgsNameMap);
                    // TODO - 执行sql、获取返回值等等
                    System.out.println(sql);
                    System.out.println(method.getReturnType()); // 获取返回值
                    System.out.println(method.getGenericReturnType()); // 获取泛型
                }
                return null;
            }
        });
        userMapper.selectUser(1, "zhangsan");
    }


    /**
     * 构造方法的参数名称与值的map
     */
    public static Map<String, Object> buildMethodArgsNameMap(Method method, Object[] args){
        Map<String, Object> nameArgMap = new HashMap<>();
        Parameter[] parameters = method.getParameters();
        int index[] = {0}; // 取巧
        Stream.of(parameters).forEach(parameter -> {
            String name = parameter.getName();
            nameArgMap.put(name, args[index[0]]);
            index[0] ++;
        });
        return nameArgMap;
    }

    /**
     * 解析sql
     */
    public static String parseSQL(String sql, Map<String, Object> nameArgsMap) {
        StringBuilder parseSQL = new StringBuilder();
        for (int i = 0; i < sql.length(); i++) {
            char c = sql.charAt(i);
            if(c == '#'){
                int nextIndex = i + 1;
                char nextChar = sql.charAt(nextIndex);
                if(nextChar != '{'){
                    throw new RuntimeException(String.format("sql异常,缺少左括号\nsql:%s", sql));
                }
                // 获取参数
                StringBuilder argSb = new StringBuilder();
                i = parseSqlArg(argSb, sql, nextIndex);
                String argName = argSb.toString();
                Object argValue = nameArgsMap.get(argName);
                if(argValue == null){
                    throw new RuntimeException(String.format("参数异常\nsql:%s", sql));
                }
                parseSQL.append(argValue);
                continue;
            }
            parseSQL.append(c);

        }


        return parseSQL.toString();
    }

    private static int parseSqlArg(StringBuilder argSb, String sql, int nextChar) {
        nextChar ++; // nextChar指向 {
        for (; nextChar < sql.length(); nextChar++) {
            char c = sql.charAt(nextChar);
            if(c != '}'){
                argSb.append(c);
                continue;
            }
            if(c == '}'){
                return nextChar;
            }
        }
        throw new RuntimeException(String.format("sql异常,缺少右括号\nsql:%s", sql));
    }
}


interface UserMapper {

    @Select("select * from user where id = #{id} and name = #{name}")
    List<User> selectUser(Integer id, String name);
}

(3)深入理解

深入理解JDK动态代理原理,使用javassist动手写一个动态代理框架

3、CGLIB动态代理

(1)火车票售票案例

同样是上面的案例,我们再次使用CGLIB代理实现。

如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

CGLIB是第三方提供的包,所以需要引入jar包的坐标:

<dependency>
	<groupId>cglib</groupId>
	<artifactId>cglib</artifactId>
	<version>2.2.2</version>
</dependency>
//火车站
public class TrainStation {
	public void sell() {
		System.out.println("火车站卖票");
	}
}
//代理工厂
public class ProxyFactory implements MethodInterceptor {
	private TrainStation station;
	public ProxyPoint(TrainStation station) {
		this.station = station;
	}
	
	public TrainStation getProxyObject() {
		//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
		Enhancer enhancer =new Enhancer();
		//设置父类的字节码对象
		enhancer.setSuperclass(target.getClass());
		//设置回调函数
		enhancer.setCallback(this);
		//创建代理对象
		TrainStation obj = (TrainStation) enhancer.create();
		return obj;
	}
	/*
	intercept方法参数说明:
		o : 代理对象
		method : 真实对象中的方法的Method实例
		args : 实际参数
		methodProxy :代理对象中的方法的method实例
	*/
	public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
		TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
		return result;
	}
}
//测试类
public class Client {
	public static void main(String[] args) {
		//创建代理工厂对象
		ProxyFactory factory = new ProxyFactory(new TrainStation());
		//获取代理对象
		TrainStation proxyObject = factory.getProxyObject();
		proxyObject.sell();
	}
}

(2)深入理解

CGLIB代理到底是个什么东西?这是一篇最全的CGLIB大全
CGLIB 动态代理实现(Spring中)

4、CGLIB和JDK动态代理对比

(1)JDK动态代理实现了被代理对象的接口,CGLIB代理继承了被代理对象。
(2)JDK动态代理和CGLIB代理都在运行期生成字节码,JDK动态代理直接写Class字节码,CGLIB代理使用ASM框架写Class字节码,CGLIB代理实现更复杂,生成代理类的速度比JDK动态代理效率低。
(3)JDK动态代理调用代理方法是通过反射机制调用的,CGLIB代理是通过FastClass机制直接调用方法的,CGLIB代理的执行效率更高。

使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理

四、源码中的代理

1、SpringAOP

SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(一)

2、MyBatis拦截器插件

MyBatis源码分析(四)插件拦截器的原理及使用

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ArcSWAT报错:Error Number :-2147467259; 对 COM 组件的调用返回了错误 HRESULT E_FAIL

文章目录 1 报错内容2 报错解决3 并行处理的设置补充说明 1 报错内容 通常为连续两段报错&#xff1a; Error Number :-2147467259 Error Message :对 COM 组件的调用返回了错误 HRESULT E_FAIL 。 Module name : mSWFlow Function name : createStream Procedure ( error li…

星辰天合参加首届数字驱动创新峰会 强调以 SDS 加速数据基础设施建设

5 月 11 日&#xff0c;2023 数字驱动创新峰会在北京新世纪日航饭店隆重举办。作为赛迪网、《数字经济》杂志社首次主办的数字驱动峰会&#xff0c;本届峰会以“新要素、新生产、新经济”为主题&#xff0c;下设数字金融创新论坛、数字制造创新论坛和数字服务创新论坛三个分论坛…

4 月 NFT 月报:在动荡的 NFT 市场中寻求生存

作者&#xff1a;lesleyfootprint.network 数据来源&#xff1a;Footprint NFT Research 上个月&#xff0c;NFT市场在 4 月 5 日出现了交易量高峰&#xff0c;随后交易量又在月底大幅下降了 50%。近期&#xff0c;NFT 卖家的数量持续超过买家的数量&#xff0c;这表明市场可…

4面华为测试开发,居然挂在这个地方....

说一下我面试别人时候的思路 反过来理解&#xff0c;就是面试时候应该注意哪些东西&#xff1b;用加粗部分标注了 一般面试分为这么几个部分&#xff1a; 一、自我介绍 这部分一般人喜欢讲很多&#xff0c;其实没必要。大约5分钟内说清楚自己的职业经历&#xff0c;自己的核…

基于Docker的深度学习环境NVIDIA和CUDA部署以及WSL和linux镜像问题

基于Docker的深度学习环境部署 1. 什么是Docker&#xff1f;2. 深度学习环境的基本要求3. Docker的基本操作3.1 在Windows上安装Docker3.2 在Ubuntu上安装Docker3.3 拉取一个pytorch的镜像3.4 部署自己的项目3.5 导出配置好项目的新镜像 4. 分享新镜像4.1 将镜像导出为tar分享给…

安卓源码下apk进行platform签名的方法

目录 一 任意目录下创建一个文件夹 二 该目录下需要准备的5个文件 三 执行命令 四 生成结果 一 任意目录下创建一个文件夹 二 该目录下需要准备的5个文件 上述五个文件&#xff0c; 前四个可以从编译好的安卓源码工程目录下复制&#xff0c; 第五个是自己需要签名的apk文件 …

抖音谋局本地生活“大蛋糕”|成都待慕电商

打开抖音APP&#xff0c;“同城”里囊括的美食、休闲娱乐、丽人美发、酒店民宿、周边旅游等让消费者们眼花缭乱&#xff0c;似乎正在打造另一个短视频版本的同城服务商。 4月25日&#xff0c;2023抖音生活服务生态伙伴大会在成都举行。《每日经济新闻》记者看到&#xff0c;活…

基于WiFi的CSI数据做呼吸频率检测-python版(含代码和数据)

一、概述 本Demo无需机器学习模型&#xff0c;Demo功能涉及的理论主要参考了硕士学位论文《基于WiFi的人体行为感知技术研究》&#xff0c;作者是南京邮电大学的朱XX&#xff0c;本人用python复现了论文中呼吸频率检测的功能。Demo实现呼吸速率检测的主要过程为&#xff1a; …

Java面试知识点(全)-设计模式三

Java面试知识点(全) 导航&#xff1a; https://nanxiang.blog.csdn.net/article/details/130640392 注&#xff1a;随时更新 18、责任链模式&#xff08;Chain of Responsibility&#xff09; 接下来我们将要谈谈责任链模式&#xff0c;有多个对象&#xff0c;每个对象持有对…

基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)

前言&#xff1a; 目前针对ARM Cortex-A7裸机开发文档及视频进行了二次升级持续更新中&#xff0c;使其内容更加丰富&#xff0c;讲解更加细致&#xff0c;全文所使用的开发平台均为华清远见FS-MP1A开发板&#xff08;STM32MP157开发板&#xff09; 针对对FS-MP1A开发板&…

Scrapy 框架介绍

一、Scrapy是什么 Scrapy 是一个基于 Twisted 的异步处理框架&#xff0c;是纯 Python 实现的爬虫框架&#xff0c;其架构清晰&#xff0c;模块之间的耦合程度低&#xff0c;可扩展性极强&#xff0c;可以灵活完成各种需求。我们只需要定制开发几个模块就可以轻松实现一个爬虫。…

索引有哪些优缺点?索引有哪几种类型?

目录 一、什么是索引&#xff1f; 二、索引的优点 三、索引的缺点 四、索引有哪几种数据类型&#xff1f; 一、什么是索引&#xff1f; 索引是一种能够帮组Mysql高效的从磁盘上检索数据的一种数据结构。在MySQL中的InnoDB引擎中&#xff0c;采取了B树的结构来实现索引和数据…

matlabR2021b启动很慢和初始化时间很长解决

工具&#xff1a;MatlabR2021b。 问题记录&#xff0c;在网上下载安装包后&#xff0c;安装后&#xff0c;发现软件启动时间很长。进入界面后软件需要较长时间的初始化。才能就绪。 查询原因为软件需要在启动是查询licence。 首先在安装文件夹中启动Activate MATLAB R2021b。…

python画直线的方法

python画直线的方法&#xff0c;下面介绍三种&#xff1a; 1、使用列表解析法&#xff0c;只需要添加一个数据类型的变量&#xff0c;然后在上面添加一系列的直线&#xff0c;即可得到一条直线。 5、使用循环解析法和 for循环解析法两种方法相结合来画直线&#xff0c;即可得到…

软件测试之jmeter性能测试让你打开一个全新的世界

一、Jmeter简介 1 概述 jmeter是一个软件&#xff0c;使负载测试或业绩为导向的业务&#xff08;功能&#xff09;测试不同的协议或技术。 它是 Apache 软件基金会的Stefano Mazzocchi JMeter 最初开发的。 它主要对 Apache JServ&#xff08;现在称为如 Apache Tomcat…

IDEA入门使用

IDEA概述 ​ IDEA全称IntelliJ IDEA&#xff0c;是用于Java语言开发的集成环境&#xff0c;它是业界公认的目前用于Java程序开发最好的工具。 集成环境&#xff1a; ​ 把代码编写&#xff0c;编译&#xff0c;执行&#xff0c;调试等多种功能综合到一起的开发工具。 为什么要…

Java基础-判断和循环

1 流程控制语句 在一个程序执行的过程中&#xff0c;各条语句的执行顺序对程序的结果是有直接影响的。所以&#xff0c;我们必须清楚每条语句的执行流程。而且&#xff0c;很多时候要通过控制语句的执行顺序来实现我们想要的功能。 1.1 流程控制语句分类 ​ 顺序结构 ​ 判…

ChatGPT 如何不使用代理,国内直连

ChatGPT 如何不使用代理&#xff0c;国内直连 本来是自己在 cloudfare 上面搭建了域名解析&#xff0c;但是最近发现了其他的方案 域名代理 最开始是参考这个文章进行的配置&#xff0c;一直用的很好 使用 Cloudflare Workers 让 OpenAI API 绕过避免被封禁 最近由于 Open…

《微服务实战》 第八章 Spring Cloud 之 Hystrix

前言 多个微服务之间调用的时候&#xff0c;假如微服务A调用微服务B和微服务C&#xff0c;微服务B和微服务C又调用其他的微服务&#xff0c;这就是所谓的"扇出"。 如果扇出的链路上某个微服务的调用响应的时间过长或者不可用&#xff0c;对微服A的调用就会占用越来越…

创新特征金字塔融合,ResNeXt引领YOLOv5高效率目标检测

目录 一、介绍1、YOLOv5简介2、ResNeXt简介3、目标检测简介 二、YOLOv5及其局限性1、YOLOv5的架构与原理2、YOLOv5的优势3、YOLOv5的局限性 三、ResNeXt与特征金字塔融合1、ResNeXt的基本原理2、ResNeXt的优势3、特征金字塔的基本原理4、特征金字塔的优势5、ResNeXt与特征金字塔…