详解静态、动态代理以及应用场景

news2025/2/25 10:27:05

一篇不太一样的代理模式详解,仔细阅读,你一定会获取不一样的代理见解,而不是人云亦云。
查看了社区里关于代理模式描述,发现很多博客千篇一律甚至存在共性错误,写此文提出自己对代理的见解。

  • 静态代理
  • 动态代理
    • JDK
    • CGLib
  • 静态代理 VS 动态代理
  • 直观的看到动态代理的模样
  • 那种只通过接口就能实现功能的技术是如何实现的

女朋友问我什么是代理,静态代理与动态代理的区别是什么,各有什么优势呢?什么场景下适合静态代理,什么场景该使用动态代理呢?真的如网上所说的,静态代理一无是处吗? 作为一个合格的男朋友,必须给她安排上,这就说道说道。

一、静态代理

1.1 静态代理架构图

静态代理类图.png

角色:

  1. 接口
  2. 被代理实现类
  3. 代理实现类

核心在于代理对象与被代理对象都需要实现同一个Interface接口,这一点也非常好理解,代理嘛 就是要代理被代理对象的所有方法。

1.2 代码案例

代码比较简单,使用静态代理为一个只有加法功能的计算器在计算前后打印日志:

/**
 * 静态代理
 */
public class StaticProxy {
    /**
     * 接口
     */
    interface Factory {
        int plus(int one, int two);
    }

    /**
     * 被代理对象
     */
    static class PlusFactory implements Factory {
        @Override
        public int plus(int one, int two) {
            return one + two;
        }
    }

    /**
     * 代理对象
     */
    static class ProxyFactory implements Factory {
        private Factory beAgentFactory;

        public ProxyFactory(Factory beAgentFactory) {
            this.beAgentFactory = beAgentFactory;
        }

        @Override
        public int plus(int one, int two) {
            try {
                System.out.println("before plus");
                return beAgentFactory.plus(one, two);
            } finally {
                System.out.println("after plus");
            }
        }
    }

    /**
     * 测试
     *
     * @param args
     */
    public static void main(String[] args) {
        // 被代理对象
        PlusFactory beAgentFactory = new PlusFactory();
        ProxyFactory proxyFactory = new ProxyFactory(beAgentFactory);
        int result = proxyFactory.plus(1, 2);
        System.out.println(result);
    }

}

**// 测试结果
before plus
after plus
3**

1.3 静态代理的缺点 以及对现有博客的抨击

目前网上关于静态代理的优缺点分析都存在着一个共性的错误。我们只有在深刻的理解静态代理与其应用场景才能发现这些错误描述。

1.3.1 千篇一律的认知错误

在国内代码社区中,搜索关于静态代理的缺点文章,几乎千篇一律的指责到:程序员要手动为每一个被代理类编写对应的代理类,如果当前系统已经有成百上千个类,工作量太大了

真的一定是这样吗?

在上述demo中,ProxyFactory类构造函数会接受一个Factory接口的实现类进行代理。因此就算此处需要新增一个被代理对象,理论上也不需要再去做一个代理对象了,因为这些被代理类都是Factory类型。这算是Java最基础的知识了!

那么什么场景下,当新创建一个被代理类时,一定需要写一个与之对应的代理类呢?

我们还用上面的demo来看,上面的demo中代理类只干了一件事:计算的前后分别打印一行日志。假设我们现在又需要写一个被代理类:HttpPlusFactory,我们希望在完成加法之后将结果通过HTTP发送给其他系统,这时我们原有的代理类ProxyFactory就显得不够用了,我们需要新建一个专门的HttpProxyFactory代理才行。

简而言之,代理存在的目的是想在不修改原有的代码为前提实现一个共性需求。即将共性的需求放入代理中实现,假设我们有不同的共性需求,我们才需要抽象出不同的代理对象。这一段话有点绕,但是我希望你能明白含义。

1.3.2 静态代理真正的缺点

抨击完国内千篇一律的错误之后,我们来谈谈静态代理有什么不太方便的地方。

  1. 代理类编写麻烦:

    这个代理对象与被代理对象一样要实现同一个接口,如果接口中有100个方法那么代理对象就得实现100个方法。

  2. 一些只有接口没有被代理类的场景无法使用静态代理:

    静态代理中,一定是要存在一个被代理对象,这对于一些只通过接口就能完成业务功能的需求很不友好,譬如:MyBatis、Feign、Dubbo

二、动态代理

动态代理,对于很多框架的实现非常友好,其诞生的目的就是让我们不去写代理对象(JDK或者CGlib帮助我们自动生成)。

2.1 实现方式

  • JDK

    基于Interface生成实现类完成代理

  • Cglib

    基于Class生成子类完成代理

2.1.1 JDK Demo

/**
 * 动态代理 - 手动编写被代理类
 */
public class DynamicProxy {

    /**
     * JDK动态代理基于接口
     */
    interface Factory {
        int plus(int one, int two);
    }

    /**
     * 被代理对象
     */
    static class PlusFactory implements Factory {
        @Override
        public int plus(int one, int two) {
            return one + two;
        }
    }

    /**
     * 代理对象
     */
    static class ProxyFactory implements InvocationHandler {

        private Factory beAgentFactory;

        public ProxyFactory(Factory beAgentFactory) {
            this.beAgentFactory = beAgentFactory;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                System.out.println("before plus");
                return method.invoke(beAgentFactory, args);
            } finally {
                System.out.println("after plus");
            }
        }
    }

    public static void main(String[] args) {
        // 1\. 被代理对象
        Factory beAgentFactory = new PlusFactory();
        // 2\. 生成动态代理
        ProxyFactory proxyFactory = new ProxyFactory(beAgentFactory);
        Factory proxyInstance = (Factory) Proxy.newProxyInstance(proxyFactory.getClass().getClassLoader(), new Class[]{Factory.class}, proxyFactory);
        // 3.调用方法
        int plus = proxyInstance.plus(1, 2);
        System.out.println(plus);
    }
}

**// 测试结果
before plus
after plus
3**

看完这个demo之后,你可能会想,这和静态代理有什么区别呢?还是要有接口、被代理类、代理类三个角色。但你要知道,如果Factory接口中如有100个抽象方法,那么代理类中只需要有一个invoke方法即可!这是和静态代理中的代理类有着本质的差别,这主要得益于动态代理中的反射机制。

当然了,如果只是这样的话,动态代理的优势还不足以让你折服,我们再来看下面这个例子:

/**
 * 动态代理 - 不需要手写被代理类
 */
public class DynamicProxy {

    /**
     * JDK动态代理基于接口
     */
    interface Factory {
        int plus(int one, int two);
    }

    static class ProxyFactory implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                System.out.println("before plus");
                if (method.getName().equals("plus")) {
                    return (int) args[0] + (int) args[1];
                }
                return null;
            } finally {
                System.out.println("after plus");
            }
        }
    }

    public static void main(String[] args) {
        // 2\. 生成动态代理
        ProxyFactory proxyFactory = new ProxyFactory();
        Factory proxyInstance = (Factory) Proxy.newProxyInstance(proxyFactory.getClass().getClassLoader(), new Class[]{Factory.class}, proxyFactory);
        // 3.调用方法
        int plus = proxyInstance.plus(1, 2);
        System.out.println(plus);
    }
}

**// 测试结果
before plus
after plus
3**

在上面这个例子中,我们直接去掉了被代理对象,而是将业务抽象到了代理类中。想一想这还是得益于反射机制吧,这时候你再对比对比静态代理的代码,是不是发现静态代理就没法这么玩了。

2.1.2 Cglib Demo

/**
 * Cglib实现动态代理
 *
 */
public class CglibProxy {

    /**
     * 被代理对象
     */
    static class PlusFactory {
        public int plus(int one, int two) {
            return one + two;
        }
    }

    /**
     * Cglib Callback
     */
    static class ProxyCallBack implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            try {
                System.out.println("before calculate");
                return methodProxy.invokeSuper(o, objects);
            } finally {
                System.out.println("after calculate");
            }
        }
    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PlusFactory.class);
        enhancer.setCallback(new ProxyCallBack());
        PlusFactory proxy = (PlusFactory) enhancer.create();
        int result = proxy.plus(1, 2);
        System.out.println(result);
    }

}

可以看到Cglib实现方式中,重点在于CallBack中,也就是此处的ProxyCallBack,其内部的intercept方法参数中也有Method、参数等信息,因此使用上和JDK Proxy感觉非常相似。

2.2 JDK Proxy VS Cglib Proxy

  • JDK Proxy是基于接口生成实现类完成代理,Cglib Proxy是基于Class生成子类完成代理。所有Cglib中被代理类中的方法不能有private、final修饰。而JDK Proxy就没有此限制,因为Java语言中接口中的方法天然不能使用private、final进行修饰。
  • 速度,这是网上传的比较多的一种比较,因为我没有做过相关实验,因此不在此定论。

三、静态代理 VS 动态代理

3.1 区别

  • 静态代理一定要编写至少一个被代理类与一个代理类,而动态代理可以不编写任何被代理类与代理类
  • 静态代理编写麻烦,因为代理类与被代理类需要实现同一个接口,如果接口有100个方法,那么就需要实现100个方法,而动态代理不需要,动态代理底层使用反射极大减少了开发量,将100个方法压缩成一个invoke方法即可。

静态代理VS动态代理.drawio.png

3.2 使用场景

3.2.1 静态代理

适合不存在共性需求的场景,比如被代理类中有100个方法,代理对象中自然也有100个方法,但是这100个方法没有共性需求,可能第一个方法是打印日志,第二个方法需要发送HTTP… 那么这时候就适合用静态代理了(假设一定需要使用代理的话)。

3.2.2 动态代理

动态代理非常适合框架底层,并且共性需求很大,参考MyBatis、Feign、Dubbo。

四、直观的看到动态代理的模样

为了加深动态代理的理解,这里以JDK动态代理为例,将上述2.1.1 JDK Demo中JDK生成的代理类打印出来。

在main方法中添加添加如下代码,最后会在项目根路径下保存生成的动态代理类:

//JDK1.8及以前的版本
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

//JDK1.8以后的版本
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

生成的代理类如下:

final class $Proxy0 extends Proxy implements Factory {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int plus(int var1, int var2) throws  {
        try {
                        // 这里的super是Proxy,里面的h属性,就是我们实现的InnvocationHandler类
            return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.example.demo.dynamic.DynamicProxy$Factory").getMethod("plus", Integer.TYPE, Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到JDK生成的代理类是实现了接口,在实现的方法中调用了父类Proxy中的InvocationHandlerinvoke方法,并且将method信息、参数信息都传过去。

五、那种只通过接口就能实现功能的技术是如何实现的

目前市面上只通编写接口就能实现功能的框架有很多,比如:MyBatisFeign等。

那么你是否也折服于他们只通过Interface就能完成功能的能力?但只要你熟练掌握动态代理的使用与原理,理解这些框架并不难。

这些框架大致的实现思路为:

  1. 肯定使用JDK动态代理,因为只有接口,没有实现类;
  2. 依赖Spring的生命周期钩子,对需要生成动态代理的接口进行代理,并将生成好的代理类放入Spring IOC中以提供后续业务的使用。

这里我们开发一个Demo,需求是只需编写Interface接口配合上一些注解完成HTTP发送。

Demo如下:

-com.example.demo
    -app
          -baseinterface
            -ann
              HttpExecute.java
              HttpService.java
            -proxy
              JDKProxy.java
            -scanner
              ClassScanner.java
            -service
              BaseInterfaceBusiness.java
  1. 定义注解:
@Documented
@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpService {
}
@Documented
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpExecute {
    String url();
}

  1. Spring生命周期钩子,扫描所有使用@HttpService注解修饰的类,并创建其代理对象并存入IOC容器中
@Component
public class ClassScanner implements BeanDefinitionRegistryPostProcessor {

    private final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "com" + "/" + DEFAULT_RESOURCE_PATTERN;
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

                    if (annotationMetadata.getAnnotationTypes().contains(HttpService.class.getName())) {
                        Class<?> aClass;
                        aClass = Class.forName(annotationMetadata.getClassName());
                        if (aClass != null) {
                            configurableListableBeanFactory.
                                    registerSingleton(
                                            toLowerCaseFirstOne(aClass.getSimpleName()),
                                            // 使用JDK动态创建代理对象
                                            JDKProxy.getInstance(aClass));
                            System.out.println("扫描到的 HttpService 接口" + aClass.getSimpleName());
                        }
                    } else {
                        continue;
                    }
                }
            }
        } catch (IOException e) {
        } catch (Exception e) {
        }
    }

    public static String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0))) {
            return s;
        } else {
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
        }
    }

}

  1. InvocationHandler实现类:
public class JDKProxy implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 对象的所有Object类,直接通过
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        HttpExecute annotation = method.getAnnotation(HttpExecute.class);
        System.out.println("发送HTTP请求: " + annotation.url());
        return "";
    }

    public static <T> T getInstance(Class<T> interfaces) {
        return (T) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), new Class[]{interfaces}, new JDKProxy());
    }

}

  1. 业务应用
@HttpService
public interface BaseInterfaceBusiness {

    @HttpExecute(url = "<https://xxxx.com>")
    void reduceInventory();

}

  1. 测试
@SpringBootTest
public class DynamicAppTest {

    @Resource
    private BaseInterfaceBusiness baseInterfaceBusiness;

    @Test
    public void baseInterfaceTest() {
        baseInterfaceBusiness.reduceInventory();
    }

}

// 结果:
// 发送HTTP请求: https://xxxx.com

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

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

相关文章

ChitGPT回答tp6写答题系统

答题系统是一个涉及多个模块的复杂系统&#xff0c;需要涉及到前端展示、后台管理、题库管理、试卷生成、考试管理等方面。下面我将简单介绍用thinkphp6框架如何实现一个基本的答题系统。 首先&#xff0c;我们需要创建数据库并设计数据表&#xff0c;包括用户表、题目表、试卷…

每日学术速递5.7

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Multimodal Procedural Planning via Dual Text-Image Prompting 标题&#xff1a;通过双文本图像提示进行多模态程序规划 作者&#xff1a;Yujie Lu, Pan Lu, Zhiyu Chen, Wanron…

微信小程序文件上传、下载和图片处理、文件操作API的使用

这次按照我的理解来做这部分的笔记 首先&#xff0c;复习上节课所学的内容。就是网络请求api的使用 现在我有一个需求就是点击按钮实现获取后端返回的图片 先打开服务器 看一下我们要返回的图片路径 书写结构 看一下返回来的数据。是在data下的banners里。因此我们封装一下 这…

【利用AI刷面试题】50道前端基础面试题

文章目录 以下是一些可能出现在前端基础面试中的问题&#xff1a;1. 如何判断当前浏览器是否支持某个 HTML5 特性&#xff1f;2. Box-sizing 属性有哪些取值&#xff0c;分别代表什么意思&#xff1f;3. 什么是浏览器的同源策略&#xff1f;4. 什么是 CORS&#xff1f;如何使用…

rabbitmq+mqtt+docker-compose搭建MQTT服务器和.netcore 客户端实现 订阅+发布

1 搭建MQTT服务器 1.1 Dockerfile 内容 FROM rabbitmq:3.11.6-management COPY install_rabbitmq_plus.sh /usr/local/ RUN chmod 777 /usr/local/install_rabbitmq_plus.sh RUN /bin/sh /usr/local/install_rabbitmq_plus.sh 1.2 容器中需要安装插件的命令 放在 insta…

假如ChatGPT 去面试前端工程师,结果会怎么样?

近日&#xff0c;有个叫 ChatGPT 的同学来我司面试前端&#xff0c;考考他面试八股文。先来问问闭包。 第一问&#xff0c;说说 JS 闭包 追问&#xff0c;详细讲讲闭包 由于篇幅太长&#xff0c;转成文字&#xff0c;以下皆为 ChatGPT 回答 闭包是一个非常重要的 JavaScript 概…

Maven构建Java项目Maven构建项目测试Maven引入外部依赖Maven项目模板

目录 Maven 构建 Java 项目 解释一下这段代码&#xff0c;为什么可以构建一个目录清晰的maven项目“mvn archetype:generate "-DgroupIdcom.companyname.bank" "-DartifactIdconsumerBanking" "-DarchetypeArtifactIdmaven-archetype-quickstart&qu…

volatile的实现简单概述

文章目录 内存屏障volatile关键字的实现synchronized关键字的实现 内存屏障 在讲解是关键字之前&#xff0c;先来了解下内存屏障的概念。 处理器保障禁止内存重排序的指令被称为基本内存屏障。其作用是禁止该屏障前后之间的操作进行重排序&#xff0c;要确保指令前的操作要先…

华为OD机试真题 Java 实现【服务中心选址】【2023Q1 100分 】

一、题目描述 一个快递公司希望在一条街道建立新的服务中心。公司统计了该街道中所有区域在地图上的位置&#xff0c;并希望能够以此为依据为新的服务中心选址&#xff0c;使服务中心到所有区域的距离的总和最小。 给你一个数组 positions&#xff0c;其中 positions[i] [le…

域名批量查询是否注册-老域名批量查询

域名到期时间批量查询软件 您是否也曾遇到过域名到期或续费时间即将到来&#xff0c;而需要手动一个一个查询每个域名的情况&#xff0c;这不仅费时效率低&#xff0c;还可能会遗漏一些域名。那么&#xff0c;我们为您介绍一款可以帮助您快速、便捷查询多个域名到期时间的软件—…

分享去年学习github命令行操作的笔记

git branch -M main 给远程分支改名 一、本地库操作 1.创建本地目录&#xff0c;用于存储要上传的文本文件。可以手动创建也可以用带命令行 mkdir <文件名> 2.进入文件夹cd <文件名> 3第一次创建时需要初始化仓库git init mac显示隐藏文件SHIFTCOMMAND. mac…

Vue3-黑马(六)

目录&#xff1a; &#xff08;1&#xff09;vue3-基础-vueuse &#xff08;2&#xff09;vue3-基础-useRequest &#xff08;3&#xff09;vue3-基础-usePagination &#xff08;1&#xff09;vue3-基础-vueuse 我们在实际中use函数的封装&#xff0c;不用我们自己写&#…

精准室内定位系统源码,采用UWB定位技术开发的智慧工厂定位系统源码

室内定位系统源码&#xff0c;采用UWB定位技术开发的智慧工厂定位系统源码 技术架构&#xff1a;单体服务 硬件&#xff08;UWB定位基站、卡牌&#xff09; 开发语言&#xff1a;java 开发工具&#xff1a;idea 、VS Code 前端框架&#xff1a;vue 后端框架&#xff1a;s…

Linux基础学习---5、磁盘查看和分区类、进程管理类

1、磁盘查看和分区类 1.1 du查看文件和目录占用的磁盘空间 du:disk usage 磁盘占用情况 1、基本语法du 目录/文件 &#xff08;显示目录下每个子目录的磁盘使用情况&#xff09; 2、情况说明选项功能-h以人们较易阅读的GBytes、MBytes、KBytes等格式自行显示-a不仅查看子目录…

iproute2 和 net-tools 介绍

路由&#xff08;Routing&#xff09; 在网络通信中&#xff0c;“路由”是一个网络层的术语。路由是指设备从一个接口上收到数据包&#xff0c;根据数据包的目的地址进行定向并转发到另一个接口的过程。路由表则是若干条路由信息的一个集合体。在路由表中&#xff0c;一条路由…

用Python+OpenCV+Yolov5+PyTorch+PyQt开发的车牌识别软件(包含训练数据)

目录 演示视频 软件使用说明 软件设计思路 演示视频 这是一个可以实时识别车牌的软件&#xff0c;支持图片和视频识别&#xff0c;以下是软件的演示视频。 车牌识别软件 点击查看代码购买地址 软件使用说明 1. 下载源码后&#xff0c;首先安装依赖库。项目所用到的依赖库已…

卷起来了!阿里最新出品“微服务全阶笔记”,涵盖微服务全部操作

近两年&#xff0c;“大厂裁员”总是凭实力冲上各大媒体头条&#xff0c;身在局中的我们早已习以为常。国内的京东&#xff0c;阿里&#xff0c;腾讯&#xff0c;字节&#xff0c;快手&#xff0c;小米等互联网公司都以不同程度的裁员比例向社会输送人才。大量有大厂经验的卷王…

一起打造漂亮的Ubuntu——2023最新版Gnome44

一起打造漂亮的Ubuntu 对于一个工程师&#xff0c;开发者&#xff0c;研究员来说&#xff0c;拥有一台漂亮的机器外加漂亮的系统界面是非常重要的。 作为Ubuntu十年的忠实粉丝&#xff0c;Linux高度依赖用户&#xff0c;无论是工作还是生活&#xff0c;我都一直以来使用着Ubu…

目标检测基础理论

一、基本知识 目标检测中RP什么意思 在目标检测中&#xff0c;RP通常指的是Recall-Precision&#xff0c;即召回率和精确率。召回率是指模型正确识别出的正样本数占所有正样本数的比例&#xff0c;而精确率是指模型正确识别出的正样本数占所有被模型识别为正样本的样本数的比例…

微服务保护——Sentinel

初识Sentinel 雪崩问题 微服务调用链路中的某个服务故障&#xff0c;引起整个链路中的所有微服务都不可用&#xff0c;这就是雪崩。 解决雪崩问题的常见方式有四种: 超时处理:设定超时时间&#xff0c;请求超过一定时间没有响应就返回错误信息&#xff0c;不会无休止等待舱壁…