Dubbo源码篇05---SPI神秘的面纱---使用篇

news2024/11/19 1:55:58

Dubbo源码篇05---SPI神秘的面纱---使用篇

  • 引言
  • Jdk提供的SPI机制
    • 基本流程
    • 缺陷
  • Dubbo的SPI机制
    • 实例演示
  • Dubbo VS JDK SPI 小结
  • Adaptive自适应扩展点
    • demo演示
    • 如何做到动态适配的
  • 按条件批量激活扩展点
  • 小结


引言

SPI全称是Service Provider Interface,其中服务提供者定义一个服务接口,并允许第三方实现进行插入。这种机制常用于预留一些关键口子或扩展点,以让调用方按照规范进行自由实现。

在 Java 开发中,JDK 提供了 SPI 机制,Dubbo中也大量使用了SPI机制,但是并没有直接使用JDK提供的SPI实现,这是为什么呢? Dubbo又是如何实现SPI机制的呢?下面一起来看看吧 !


Jdk提供的SPI机制

核心思想:

  • SPI的全名为Service Provider Interface,面向对象的设计里面,模块之间推荐基于接口编程,而不是对实现类进行硬编码,这样做也是为了模块设计的可拔插原则。
  • 为了在模块装配的时候不在程序里指明是哪个实现,就需要一种服务发现的机制,jdk的spi就是为某个接口寻找服务实现。
  • jdk提供了服务实现查找的工具类:java.util.ServiceLoader,它会去加载META-INF/service/目录下的配置文件。

基本流程

Jdk提供的SPI机制实现流程如何下所示:

  1. 指明要加载哪个服务接口的第三方实现类
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
  1. 使用线程上下文类加载器来加载第三方实现类,目的是为了打破双亲委派机制的局限性
    public static <S> ServiceLoader<S> load(Class<S> service) {
        //线程上下文累类加载器默认为应用程序上下文类加载器,也被称为系统类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

JDK暴露的很多顶层服务接口使用的是启动类加载器进行加载,但是启动类加载器显然无法加载第三方提供的服务接口实现,因此不得不借助线程上下文类加载器来打破双亲委派加载机制。

  1. 每个ServiceLoader实例负责管理当前服务接口的实现类集合
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        //如果我们将当前线程上下文类加载器设置为null,那么默认还是会取应用程序上下文类加载器
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
    //缓存被实例化好的第三方服务接口实现类,在集合中按照实例化的顺序存储
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    //懒加载迭代器
    private LazyIterator lookupIterator;
   
    public void reload() {
        //清空provider集合
        providers.clear();
        //初始化LazyIterator
        lookupIterator = new LazyIterator(service, loader);
    }

LazyIterator表明会将加载第三方提供的资源文件的操作推迟到第一次获取时进行。

    private class LazyIterator
        implements Iterator<S>
    {
        //暴露的服务接口
        Class<S> service;
        //负责加载第三方服务接口实现类的加载器
        ClassLoader loader;
        //所有第三方服务接口实现类的资源文件位置
        Enumeration<URL> configs = null;
        //存放所有待处理的第三方服务提供者的实现类全类名的集合
        Iterator<String> pending = null;
        //下一个待处理的第三方服务提供者的实现类全类名
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }
       ...
  1. 利用LazyIterator迭代器提供的方法遍历第三方提供的服务接口实现类
private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    //"META-INF/services/"+暴露的服务接口全类名 
                    String fullName = PREFIX + service.getName();
                    //利用类加载器从类路径下搜索指定SPI资源文件
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            //在Java SPI中,配置文件的格式要求每行只包含一个服务实现类的完整类名,并且不包含任何注释或空格。
            //等到当前config对应的SPI文件中涉及到的所有服务实现类全部被遍历完后,开始处理下一个SPI文件
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                //按顺序解析每个待处理的第三方服务提供方实现类集合,解析后返回的是第三方提供的实现类全类名(每调用hasNextService方法,向后解析一次)
                //parse方法解析当前SPI文件,获取文件中提供的所有实现类全类名,
                //依次判断当前第三方实现类全类名是否已经存在于providers集合中,如果存在则跳过
                //如果不存在,则加入providers集合中
                pending = parse(service, configs.nextElement());
            }
            //nextName拿到下一个需要处理的第三方实现类全类名
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            //拿到当前待处理的第三方实现类全类名
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //尝试实例化第三方实现类,可能存在找不到实现类的情况
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            //实现类必须是暴露的服务接口的实现类
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                //通过无参构造直接实例化一个第三方实现类出来,然后加入服务提供者集合中
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

JDK SPI流程简单概括:

  • ServiceLoader类是JDK SPI机制实现的核心,其中内部类LazyIterator作为对外暴露的资源懒加载迭代器,提供了hasNextService和nextService方法用于遍历第三方提供的服务实现类
  • hashNextService初次调用时,会根据接口的名称获取所有jar、classes中 META-INF/services 下的配置文件
  • hashNextService每次调用时,当上一个SPI文件中涉及到的实现类都遍历完后,会按顺序解析下一个SPI文件,获取SPI文件中提供的所有实现类全类名,然后依次赋值给nextName进行处理
  • nextService每次调用时,都会获取nextName,然后实例化一个实例返回,同时会加入providers集合,避免对同一个全类名的两次实例化,确保其单例性

providers集合只在hasNextService方法的parse中被使用到,用于避免重复实例化全类名相同的两个第三方实现类


缺陷

JDK提供的SPI机制缺陷如下:

  • 只支持迭代器的单向遍历,一旦遍历结束,再次遍历需要重新调用load
  • 不提供缓存已经创建出来的对象的操作,每次load调用都会重新创建和加载相关对象和资源

如何解决上面两个问题呢?

  • 增加缓存,来降低磁盘IO访问和对象的创建
  • 使用Map的hash查找,提升检索指定实现类的性能

Dubbo的SPI机制

Dubbo SPI相较于 JAVA SPI 有如下几个增强点:

  • JAVA SPI 是一次性加载了所有的的实现类,但是很多时候我们这些实现类并不会被用到,Dubbo SPI在配置文件中为每个实现类指定key,可通过key去加载对应的实现类,实现了按需加载
  • JAVA SPI 一次性获取出来所有的实现类,但是我们无法对他进行分类,也就是说我们无法确定究竟需要使用哪个实现类。
  • Dubbo SPI 增加扩展类的 IOC 能力。Dubbo 的扩展能力并不仅仅只是发现扩展服务实现类,而是在此基础上更进一步,如果该扩展类的属性依赖其他对象,则 Dubbo 会自动的完成该依赖对象的注入功能。
  • Dubbo SPI 增加扩展类的 AOP 能力。Dubbo 扩展能力会自动的发现扩展类的包装类,完成包装类的构造,增强扩展类的功能。

Dubbo 设计出了一个 ExtensionLoader 类,实现了 SPI 思想,也被称为 Dubbo SPI 机制。

SPI扩展机制实现的结构目录如下所示:
在这里插入图片描述


实例演示

Dubbo SPI简单使用如下:

  • 定义一个 IDemoSpi 接口,并在该接口上添加 @SPI 注解。

在某个接口上加上@SPI注解后,表明该接口为可扩展接口,这就像RMI中暴露的远程服务接口需要继承Remote接口,JDK可序列化类需要继承Serializable接口一样,只起到标记作用。

@SPI
public interface IDemoSpi {
    String hello();
}
  • 定义一个 CustomSpi 实现类来实现该接口,然后通过 ExtensionLoader 的 getExtension 方法传入指定别名来获取具体的实现类。
public class CustomSpi implements IDemoSpi {
    @Override
    public String hello() {
        return "hello dubbo spi";
    }
}
  • “/META-INF/services/com.dhy.IDemoSpi”这个资源文件中,添加实现类的类路径,并为类路径取一个别名(customSpi)。
customSpi=com.dhy.CustomSpi 
  • 使用ExtensionLoader加载扩展接口实现类
public class DubboSpiTest {
    @Test
    void dubboSpiTest() {
        ApplicationModel applicationModel = ApplicationModel.defaultModel();
        ExtensionLoader<IDemoSpi> extensionLoader = applicationModel.getExtensionLoader(IDemoSpi.class);
        // 通过指定的名称从加载器中获取指定的实现类
        IDemoSpi customSpi = extensionLoader.getExtension("customSpi");
        System.out.println(customSpi + ", " + customSpi.hello());
        // 再次通过指定的名称从加载器中获取指定的实现类,看看打印的引用是否创建了新对象
        IDemoSpi customSpi2 = extensionLoader.getExtension("customSpi");
        System.out.println(customSpi2 + ", " + customSpi2.hello());
    }
}

在这里插入图片描述


Dubbo VS JDK SPI 小结

JDK SPI 的使用三部曲:

  • 首先,定义一个接口。
  • 然后,定义一些类来实现该接口,并将多个实现类的类路径添加到“/META-INF/services/ 接口类路径”文件中。
  • 最后,使用 ServiceLoader 的 load 方法加载接口的所有实现类。

但 JDK SPI 在高频调用时,可能会出现磁盘 IO 吞吐下降、大量对象产生和查询指定实现类的 O(n) 复杂度等问题,采用缓存 +Map 的组合方式来解决,就是 Dubbo SPI 的核心思想。

Dubbo SPI 的使用步骤三部曲:

  • 首先,同样也是定义一个接口,但是需要为该接口添加 @SPI 注解。
  • 然后,定义一些类来实现该接口,并将多个实现类的类路径添加到“/META-INF/services/ 接口类路径”文件中,并在文件中为每个类路径取一个别名。
  • 最后,使用 ExtensionLoader 的 getExtension 方法就能拿到指定的实现类使用。

Adaptive自适应扩展点

上面讲到,Dubbo提供的SPI机制对原生JDK提供的SPI机制主要有三个改进:

  • O(1)复杂度获取SPI接口实现类
  • 缓存SPI实现接口实现类
  • 按需加载指定的实现类

其实Dubbo除了以上三个改进外,还提供了在程序运行时根据RPC请求上下文中的URL中携带的信息进行动态选择SPI接口实现类的功能,该功能就是本节重点要讲解的Adaptive动态适配。

dubbo以URL为总线,运行过程中所有的状态数据信息都可以通过URL来获取,比如当前系统采用什么序列化,采用什么通信,采用什么负载均衡等信息,都是通过URL的参数来呈现的,所以在框架运行过程中,运行到某个阶段需要相应的数据,都可以通过对应的Key从URL的参数列表中获取。

demo演示

我们可以在SPI服务接口中需要动态适配的接口方法上标注@Adaptive注解,并且方法参数类型为URL,对于不需要在运行时通过RPC请求上下文中URL携带信息进行动态SPI实现类选择的接口方法而言,则无需标注@Adaptive注解:

@SPI("spring")
public interface FrameWork {
    @Adaptive
    String getName(URL url);
    String getInfo();
}

分别提供下面这些SPI接口实现类:

public class Spring implements FrameWork{
    @Override
    public String getName(URL url) {
        return "spring";
    }

    @Override
    public String getInfo() {
        return "流行的Spring框架";
    }
}
public class SpringBoot implements FrameWork{
    @Override
    public String getName(URL url) {
        return "springBoot";
    }

    @Override
    public String getInfo() {
        return "自动化的SpringBoot框架";
    }
}
public class Guice implements FrameWork{
    @Override
    public String getName(URL url) {
        return "guice";
    }

    @Override
    public String getInfo() {
        return "google 开源的轻量级IOC框架";
    }
}

Dubbo SPI配置文件:
在这里插入图片描述

spring=com.adaptive.Spring
springBoot=com.adaptive.SpringBoot
guice=com.adaptive.Guice

测试类:

class AdaptiveTest {
    @Test
    void adaptiveTest() {
        //URL中通过frame.work作为key,指明运行时需要获取的接口实现类为SPI配置文件中key=spring的实现类
        ApplicationModel applicationModel = ApplicationModel.defaultModel();
        ExtensionLoader<FrameWork> extensionLoader = applicationModel.getExtensionLoader(FrameWork.class);
        FrameWork frameWork = extensionLoader.getAdaptiveExtension();
        print(frameWork.getName(URL.valueOf("dubbo://127.0.0.1:80/?frame.work=spring")));
        //URL中通过frame.work作为key,指明运行时需要获取的接口实现类为SPI配置文件中key=springBoot的实现类
        print(frameWork.getName(URL.valueOf("dubbo://127.0.0.1:80/?frame.work=springBoot")));
        //URL中通过frame.work作为key,指明运行时需要获取的接口实现类为SPI配置文件中key=guice的实现类
        print(frameWork.getName(URL.valueOf("dubbo://127.0.0.1:80/?frame.work=guice")));
        //URL中不通过frame.work指明需要的第三方实现类,此时改为选择默认实现--SPI注解中指定的value值
        print(frameWork.getName(URL.valueOf("dubbo://127.0.0.1:80/")));
    }

    private void print(String name) {
        System.out.println("dubbo SPI动态适配拿到的结果为: "+name);
    }
}

运行结果:
在这里插入图片描述


如何做到动态适配的

Dubbo能够对SPI实现类进行动态选择,做到动态适配,其实是对getAdaptiveExtension方法返回扩展类实例进行了一层动态代理,在动态代理类内部处理了根据URL参数进行动态选择实现类的逻辑。

在这里插入图片描述

我们这里以上文列举的FrameWork接口为例,看看dubbo为其返回生成的代理对象实例具体代码:

public class FrameWork$Adaptive implements FrameWork {
    public String getName(URL uRL) {
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        //从URL中取出key为frame.work的键值对,key为服务接口类名按照大写字母分割成多个单词,并以"."相连接
        //val就是要选择的第三方扩展实现类---默认值为SPI注解中指明的val值
        String string = uRL.getParameter("frame.work", "spring");
        //如果url中没有获取到key=frame.work的键值对,并且SPI注解中没有指定默认值,那么会抛出异常
        if (string == null) {
            throw new IllegalStateException("Failed to get extension (com.adaptive.FrameWork) name from url (" + uRL.toString() + ") use keys([frame.work])");
        }
        ScopeModel scopeModel = ScopeModelUtil.getOrDefault(uRL.getScopeModel(), FrameWork.class);
        //调用ExtensionLoader的getExtension方法,根据key获取扩展实现类
        FrameWork frameWork = scopeModel.getExtensionLoader(FrameWork.class).getExtension(string);
        //调用扩展实现类的getName方法
        return frameWork.getName(uRL);
    }
    
    //未加@Adaptive注解的方法,表明无需运行时动态适配,那么dubbo不会为其生成动态选择代码逻辑,而是抛出不支持操作的异常信息
    public String getInfo() {
        throw new UnsupportedOperationException("The method public abstract java.lang.String com.adaptive.FrameWork.getInfo() of interface com.adaptive.FrameWork is not adaptive method!");
    }
}

可以看到所谓动态适配,就是利用代理对象将根据URL中携带的参数来选择扩展实现类的代码模板化了而已。

如果后期对于某个扩展接口,我们不想根据URL进行动态适配了,而是想要自定义动态匹配规则,或者想要指定一个实现类作为扩展点,这时候我们可以将@Adaptive注解加在对应实现类上,如下所示:

@Adaptive
public class Guice implements FrameWork{
    @Override
    public String getName(URL url) {
        return "guice";
    }

    @Override
    public String getInfo() {
        return "google 开源的轻量级IOC框架";
    }
}

此时,再运行上面的测试用例,结果如下:
在这里插入图片描述
此时,动态适配就不会生效了,只会选择标注了@Adaptive注解的实现类作为自适应扩展点,我们可以在自适应扩展点中实现自定义的动态匹配逻辑。

@Adaptive注解最多只能标注在某个扩展接口的某一个实现类上,如果大于一个,则会抛出异常 (默认情况下)


按条件批量激活扩展点

上文我们看到的都是根据key查找唯一个实现类的场景,但是如果有如下一个需求:

  • 该需求要求我们获取满足某个条件的所有扩展实现类,又怎么实现呢?

dubbo使用@Activate实现了上面的需求,@Activate标注在扩展实现类上,用于说明当前实现类满足什么条件方可被激活:

public @interface Activate {
    String[] group() default {};
    String[] value() default {};
    //既然可以获取满足条件的所有实现类,那么必然涉及排序问题,这里的order就是多个扩展实现类用于排序的依据
    int order() default 0;
}

对于满足条件的定义,这里的条件dubbo精确定义了一个group字段,用于指明当前扩展实现类属于什么分组,这里的分组一般用来区分当前是客户端还是服务端。

例如下面这段代码表示如果当前为所处环境为PROVIDER服务提供方,则激活当前扩展实现类:

@Activate(group =  CommonConstants.PROVIDER)
public class Guice implements FrameWork{
    @Override
    public String getName(URL url) {
        return "guice";
    }

    @Override
    public String getInfo() {
        return "google 开源的轻量级IOC框架";
    }
}

value字段则用于用户自定义激活条件,例如下面这段代码表示URL参数中携带了guice键值对时,并且当前所处环境为PROVIDER时,才会激活当前扩展实现类:

@Activate(value = {"guice"},group =  CommonConstants.PROVIDER)
public class Guice implements FrameWork{
    @Override
    public String getName(URL url) {
        return "guice";
    }

    @Override
    public String getInfo() {
        return "google 开源的轻量级IOC框架";
    }
}

下面给出一个简单的测试案例, 基于上面给出的测试用例,我们将@Adaptive注解统一替换为@Activate注解,并且注解中的value统一赋值为framework,表示当URL中携带了key为frameWork的键值对时,激活下面的扩展实现类:

@Activate("frameWork")
public class Guice implements FrameWork{
    @Override
    public String getName(URL url) {
        return "guice";
    }

    @Override
    public String getInfo() {
        return "google 开源的轻量级IOC框架";
    }
}
//另外两个类处理方式一致
...

测试类:

class ActivateTest {
    @Test
    void activateTest() {
        //URL中通过frame.work作为key,指明运行时需要获取的接口实现类为SPI配置文件中key=spring的实现类
        ApplicationModel applicationModel = ApplicationModel.defaultModel();
        ExtensionLoader<FrameWork> extensionLoader = applicationModel.getExtensionLoader(FrameWork.class);
        List<FrameWork> frameWorkList = extensionLoader.getActivateExtension(URL.valueOf("dubbo://127.0.0.1:80/?frameWork=true"), "", "");
        frameWorkList.forEach(frameWork -> {
            System.out.println(frameWork.getInfo());
        });
    }
}

在这里插入图片描述

通过上面的测试用例可知,@Activate注解可以在SPI服务发现的场景下,提供按条件批量激活的功能,相比于@Adaptive注解而言,增加了按条件批量激活的功能。


小结

本文主要给大家讲解了一下dubbo spi的基本使用和相关扩展,下篇文章将会深入源码探究其背后的实现。

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

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

相关文章

全面提升测试效率,一键实现多文件、多Sheet的WEB自动化测试!

目录 前言&#xff1a; 设计目标 框架结构 实现 总结 前言&#xff1a; 在WEB开发中&#xff0c;自动化测试框架是一个不可或缺的组件。封装一个既能支持多文件&#xff0c;又能支持多Sheet的WEB自动化框架&#xff0c;将会极大地提升我们的开发效率。下面我将会详细介绍…

GPT-4平替版:MiniGPT-4,支持图像理解和对话,现已开源

项目地址&#xff1a;https://minigpt-4.github.io/ 论文链接&#xff1a;https://github.com/Vision-CAIR/MiniGPT-4/blob/main/MiniGPT_4.pdf 代码&#xff1a;https://github.com/Vision-CAIR/MiniGPT-4 视频&#xff1a;https://youtu.be/__tftoxpBAw 数据集&#xff…

el-dialog 关闭再打开后窗口内容不刷新问题

页面中有增加和编辑两个功能,由于弹窗样式都是一样的,于是将它拆分成一个子组件,父组件把状态传给子组件,子组件根据这个状态判断是做编辑操作还是新增操作. 编辑 添加 问题一:但是这样遇到了一个问题,在编辑时&#xff0c;只有第一次点编辑时&#xff0c;回显的数据才能正确显…

大学生就业工资低,想转行IT?0基础培训班学习半年云计算出来可以就业吗?挑战高薪职业!

大学生就业工资低&#xff0c;想转行IT&#xff1f;0基础学习云计算可以就业吗&#xff1f; 大学生就业工资低&#xff0c;想转行IT&#xff1f;0基础培训班学习半年云计算出来可以就业吗&#xff1f;这是一个很常见的问题&#xff0c;也是很多大学毕业生关心的话题。根据我了解…

探索2023年海外网红营销合作方式:提升品牌曝光度的创新策略

随着社交媒体的崛起和用户对网红的追捧&#xff0c;海外网红营销已经成为品牌推广的不可忽视的一部分。在2023年&#xff0c;有7种最火爆的海外网红营销合作方式备受瞩目。本文Nox聚星将和大家一起来详细了解这7种方式&#xff0c;为品牌提供更多营销灵感和策略。 1、跨平台合作…

Codeforces Round 834 (Div. 3)

题集链接 Codeforces Round 834 A. Yes-Yes?B. Lost PermutationC. Thermostat A. Yes-Yes? Example input 12 YES esYes codeforces es se YesY esYesYesYesYesYesYe seY Yess sY o Yesoutput NO YES NO YES NO YES YES NO NO YES NO YES题意&题解&#xff1a; 其实就…

Windows环境下安装及部署Nginx教程(含多个站点部署)

目录 一、下载安装Nginx 二、部署Nginx 三、多站点部署的情况 1、nginx域名解析&#xff0c;虚拟主机&#xff1a; 四、带https的站点如何部署&#xff0c;与http的有何不同点&#xff1f; 一、下载安装Nginx 1、官网下载地址&#xff1a;https://nginx.org/en/download.h…

2022 年第四届河南省 CCPC 大学生程序设计竞赛vp补题

Dashboard - 2022 CCPC Henan Provincial Collegiate Programming Contest - Codeforces Problem B. Hash 思路&#xff1a; 发现31的次幂取模的答案&#xff0c;所以如果一段太长肯定不如拆成2段。首先如果一段长度为7,那么无论他的开头是a,eh,n的谁,都有val>31^6887503…

0基础学习VR全景平台篇第29章:场景功能-音乐解说

本期为大家带来蛙色VR平台&#xff0c;场景管理模块-音乐功能&#xff01; 功能位置示意 一、本功能将用在哪里&#xff1f; 优秀VR全景作品不仅注重视觉的体验&#xff0c;接入契合场景的背景音乐与解说&#xff1b; 可将音乐与解说进行全局播放或进行分场景播放&#xff0…

前端学习--Vue(4) 生命周期

一、组件的生命周期 一个组件从创建-运行-销毁的真个阶段&#xff0c;强调的是一个时间段 1.1 生命周期函数 1.1.1 创建 &#xff08;只执行一次&#xff09; created() 阶段任务&#xff1a;最早可以使用methods中的方法发起ajax请求获取数据&#xff0c;并将数据挂载到d…

论文阅读笔记(三)——有监督解耦+信息瓶颈

论文信息 《Disentangled Information Bottleneck》 论文地址&#xff1a;https://ojs.aaai.org/index.php/AAAI/article/view/17120 代码地址&#xff1a;GitHub - PanZiqiAI/disentangled-information-bottleneck inproceedings{pan2021disentangled, title{Disentangled in…

vue3与vue2共存环境搭建

1、全局安装vue2 npm install vue-cli -g2、自行在任意位置创建一个文件夹&#xff0c;局部安装vue3 npm初始化 npm initnpm初始化 提示&#xff1a; 初始化后 出现文件package.json 如果没有初始化 会报错&#xff0c;且文件夹中不会新增内容 3、局部安装vue3 npm install …

一名优秀的黑客,具备的有哪些特质

想要成为网络hacker黑客&#xff1f;十个必会的特质 一、基本的计算机知识 把它列为第一条&#xff0c;相信很多人肯定会觉得不以为然&#xff0c;其实掌握必要的计算机知识对黑客入门非常重要。这些包括&#xff1a;计算机硬件的组成、操作系统的安装、Windows批处理命令、命…

LeetCode_DFS_困难_1377.T 秒后青蛙的位置

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 给你一棵由 n 个顶点组成的无向树&#xff0c;顶点编号从 1 到 n。青蛙从 顶点 1 开始起跳。规则如下&#xff1a; 在一秒内&#xff0c;青蛙从它所在的当前顶点跳到另一个未访问过的顶点&#xff08;如果它…

apt remove purge的区别 删除包的同时删除配置文件

1、apt remove purge的区别 查看 man apt apt remove&#xff1a;删除软件包&#xff0c;不删除配置文件。这么做的目的是将来再次安装这个包时 原来的配置文件会自动加载供使用。也可以避免误删除包&#xff0c;配置文件还在的话&#xff0c;重新安装一次软件包就可以恢复到…

亚马逊云科技出海日6月9日盛夏盛启

向全球价值链上游奋进 中国企业增强国际竞争力的关键&#xff0c;是努力朝全球价值链上游奋进&#xff0c;发力技术出海。中国的出海新机遇&#xff0c;背后曾是疫情在全球按下数字互联和数字化升级的快进键&#xff0c;跨境电商、在线社交、移动支付、数字服务等数字经济迎来…

Spring 学习总结(37)—— 了解什么是单体的模块化,Spring Modulith 入门实践

1、介绍 模块化单体是一种架构风格,代码是根据模块的概念构成的。 对于许多组织而言,模块化单体可能是一个很好的选择。 它有助于保持一定程度的独立性,这有助于我们在需要的时候轻松过渡到微服务架构。Spring Modulith 是 Spring 的一个实验项目,可用于构建模块化单体应用…

《消息队列高手课》课程笔记(一)

消息生态系统全景图 为什么需要消息队列&#xff1f; 异步处理 大多数程序员在面试中&#xff0c;应该都问过或被问过一个经典却没有标准答案的问题&#xff1a;如何设计一个秒杀系统&#xff1f; 这个问题可以有一百个版本的合理答案&#xff0c;但大多数答案中都离不开消息…

马蹄集oj赛(第五次)

目录 围栏木桩 某农场有一个由按编号排列的根木桩构成的首尾不相连的围栏。现要在这个围栏中选取一些木桩&#xff0c;按照原有的编号次序排列之后&#xff0c;这些木桩高度成一个升序序列。 大厨小码哥 附庸的附庸 最长子段和 旅费 纸带 暧昧团 上楼梯 上楼梯2 采蜜 围栏…

Spring 学习总结(36)—— Spring 状态机优雅实践

1、什么是状态机 1.1 什么是状态 先来解释什么是“状态”( State )。现实事物是有不同状态的,例如一个自动门,就有 open 和 closed 两种状态。我们通常所说的状态机是有限状态机,也就是被描述的事物的状态的数量是有限个,例如自动门的状态就是两个 open 和 closed 。 状…