iis服务器助手广告广告
返回顶部
首页 > 资讯 > 服务器 >Dubbo 系列JDK SPI 原理解析
  • 398
分享到

Dubbo 系列JDK SPI 原理解析

Dubbo JDK SPI原理Dubbo JDK SPI 2023-02-24 12:02:19 398人浏览 薄情痞子
摘要

目录正文为什么要使用SPI?什么是 SPIjdk SPI 机制JDK SPI原理为什么不直接使用 JDK SPI正文 上一篇文章讲到了如何去找到 dubbo 源码的调试入口,如果你

正文

上一篇文章讲到了如何去找到 dubbo 源码的调试入口,如果你跟随文章思路,那你将要阅读的第一条主线将是 Dubbo 的服务发布流程。在阅读的过程中你会发现,有很多代码很相似,并且重复出现,比如这里:

private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
……
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDatainvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
exporters.add(exporter);
……

这段代码是 Dubbo 服务发布的关键流程,其中用到了两个类都是通过ExtensionLoader.getExtensionLoader()去获得的,这其实体现的就是Dubbo 的SPI机制,SPI 机制在 Dubbo 中被大量运用,也是 Dubbo 设计的亮点所在。

为什么要使用SPI?

这就要谈到Dubbo的架构设计了,之前提到 Dubbo 采用的是分层架构的方式,Dubbo 的设计体现了程序设计中的开闭原则,每一层都可以被另一种实现技术替换掉,而不影响上下层之间的依赖和整体逻辑的运转,这其实就是微内核架构(微内核+插件)。

微内核架构也被称为插件化架构(Plug-in Architecture),这是一种面向功能进行拆分的可扩展性架构。内核功能是比较稳定的,只负责管理插件的生命周期,不会因为系统功能的扩展而不断进行修改。功能上的扩展全部封装到插件之中,插件模块是独立存在的模块,包含特定的功能,全部可被替换,并且可以拓展内核系统的功能,而 Dubbo 最终决定采用 SPI 机制来加载插件。

开闭原则 OCP (Open-Closed Principle ):程序的设计应该是不约束扩展,即扩展开放,但又不能修改已有功能,即修改关闭。

什么是 SPI

SPI ,全称为 Service Provider Interface,直接翻译过来是服务提供者接口,是一种服务发现机制,而我们通常指的是JDK的SPI。

JDK SPI,它是JDK内置的一种服务发现机制,可以动态的发现服务,即服务提供商,它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。

根据他的定义就知道他主要是被框架开发人员使用的,通过 SPI 可以加载服务本身以外的扩展。最常用的比如JDBC驱动连接时候选择不同的驱动,对java.sql.Driver的实现就利用了SPI机制;spring框架中也使用了很多,比如在 Spring 中为了支持Servlet3.0规范不使用WEB.xml,对javax.servlet.ServletContainerInitializer的实现也利用了SPI;在Dubbo中更是大量运用了SPI机制,不但有JDK SPI的运用,更重要的是 Dubbo 自己还实现了一套SPI机制。

JDK SPI 机制

当服务提供者想利用SPI机制去扩展,需要遵循以下步骤。

  • 首先需要实现实现对应接口。
  • 然后需要在 Classpath 下的 META-INF/services/ 目录中创建一个以服务接口全路径命命名的文件。
  • 在该文件中记录服务接口的所有具体实现类,通常是在外部引入的扩展包中,比如引入JDBC的jar包。
  • 做好以上配置,就可以利用JDK SPI的查找机制在META-INF/services/文件夹下根据配置来对具体的实现类加载和实例化。

如果看完上述流程还不是很清楚,请看如下示例。比如这里有接口 MySPI 需要按照上述流程按照SPI机制加载。

首先提供了两个实现类 GoodbyeMySPI 和 HelloMySPI,然后在 META-INF/services 文件夹下保存了文件org.daley.spi.demo.MySPI,文件的内容是两个实现类的全路径名。就绪之后就可以在main方法中启动demo,用ServiceLoader.load()加载 MySPI 的两个实现类,然后分别调用接口方法执行。代码如下:


public interface MySPI {
    void say();
}
public class HelloMySPI implements MySPI{
    @Override
    public void say() {
        System.out.println("HelloMySPI say:hello");
    }
}
public class GoodbyeMySPI implements MySPI {
    @Override
    public void say() {
        System.out.println("GoodbyeMySPI say:Goodbye");
    }
}
public static void main(String[] args) {
    ServiceLoader<MySPI> serviceLoader = ServiceLoader.load(MySPI.class);
    Iterator<MySPI> iterator = serviceLoader.iterator();
  // 开始迭代执行
    while (iterator.hasNext()) {
        MySPI spi = iterator.next();
        spi.say();
    }
}
//配置文件 org.daley.spi.demo.MySPI
org.daley.spi.demo.GoodbyeMySPI
org.daley.spi.demo.HelloMySPI
//输出如下:
//GoodbyeMySPI say:Goodbye
//HelloMySPI say:hello

JDK SPI原理

看完上述 demo,你有没有好奇 JDK SPI 的原理是什么?相信你已经猜的八九不离十了。我们从demo的main方法开始追踪起。

很明显关键的代码就一行ServiceLoader.load(MySPI.class),他是整个类加载的入口。

  • 调用ServiceLoader.load(MySPI.class)开始加载,并且会拿到当前线程的类加载器。
  • 开始创建一个ServiceLoader,最终可以追踪到调用reload()方法
public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}
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");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
  // 加载
    reload();
}

reload()方法中首先清空了providers,它里面存贮了所有服务接口的实现,key为实现类名,value为对象。然后便会new一个LazyIteratorLazyIteratorServiceLoader内部实现的一个迭代器,此时还没有真正开始加载,只是保存了接口和类加载器在迭代器中。

private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}
private class LazyIterator implements Iterator<S>{
  private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
  }
}

正如其名懒加载迭代器,在调用iterator.hasNext()时才真正发生加载。在hasNextService()方法中,第一次调用此方法会先拼接出配置SPI的文件名,在本demo中也就是META-INF/services/org.daley.spi.demo.MySPI,然后会使用类加载器加载配置文件并且将每行的内容设置到迭代器pending中,每次遍历都可以按行依次拿到实现类的名字。

比如第一次迭代,返回第一行配置的实现类名org.daley.spi.demo.GoodbyeMySPI。到这里只是加载配置文件拿到类名,还未加载类。

public boolean hasNext() {
    if (acc == null) {
        return hasNextService();
    } else {
      ……
    }
}
// pengding中保存了配置文件中的实现类名,按行迭代
Iterator<String> pending = null;
private boolean hasNextService() {
      ……
    if (configs == null) {
        try {
          // 设置配置文件路径 META-INF/services/org.daley.spi.demo.MySPI
            String fullName = PREFIX + service.getName();
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
  // 第一次遍历 pendind=null
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        pending = parse(service, configs.nextElement());
    }
  // 按行拿到实现类名
    nextName = pending.next();
    return true;
}

当执行到iterator.next()的时候才会真正去加载class类。追踪此方法最终进入nextService()方法,在这里你会看到熟悉的Class.forName()以及newInstance()方法,读取类和实例化类的逻辑一目了然。到这里JDK SPI的核心逻辑就结束了。

public S next() {
    if (acc == null) {
        return nextService();
    } else {
        ……
    }
}
private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
        //当前正在迭代的实现类名
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
      // 根据类路径加载class
        c = Class.forName(cn, false, loader);
    ……
      // 实例化实现类,并且保存在providers中
        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
}

简言之,通过将实现类名保存在以服务接口命名的配置文件中,放置在META-INF/servicesServiceLoader会在先读取配置文件中实现类的名字,然后根据调用newInstance()方法对其进行实例化。简化的原理图如下:

为什么不直接使用 JDK SPI

既然已经有了 JDP SPI 为什么还需要 Dubbo SPI呢?

技术的出现通常都是为了解决现有问题,通过之前的 demo,不难发现 JDK SPI 机制就存在以下一些问题:

  • 实现类会被全部遍历并且实例化,假如我们只需要使用其中的一个实现,这在实现类很多的情况下无疑是对机器资源巨大的浪费,
  • 无法按需获取实现类,不够灵活,我们需要遍历一遍所有实现类才能找到指定实现。

所以 Dubbo SPI 以 JDK SPI 为参考做出了改进设计,进行了性能优化以及功能增强,Dubbo SPI 机制的出现解决了上述问题。 由于 Dubbo SPI的知识点太多并且很重要,将专门安排在下一篇文章讲解,更多关于Dubbo JDK SPI原理的资料请关注编程网其它相关文章!

--结束END--

本文标题: Dubbo 系列JDK SPI 原理解析

本文链接: https://www.lsjlt.com/news/197439.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Dubbo 系列JDK SPI 原理解析
    目录正文为什么要使用SPI?什么是 SPIJDK SPI 机制JDK SPI原理为什么不直接使用 JDK SPI正文 上一篇文章讲到了如何去找到 Dubbo 源码的调试入口,如果你...
    99+
    2023-02-24
    Dubbo JDK SPI原理 Dubbo JDK SPI
  • Dubbo系列JDK SPI原理是什么
    这篇文章主要介绍“Dubbo系列JDK SPI原理是什么”,在日常操作中,相信很多人在Dubbo系列JDK SPI原理是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Dubbo系列JD...
    99+
    2023-07-05
  • Java和Dubbo的SPI机制原理解析
    SPI: 简单理解就是,你一个接口有多种实现,然后在代码运行时候,具体选用那个实现,这时候我们就可以通过一些特定的方式来告诉程序寻用那个实现类,这就是SPI。 JAVA的SPI 全称...
    99+
    2024-04-02
  • dubbo的SPI应用与原理是什么
    dubbo的SPI应用与原理是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。dubboSPI(Service Provider Interface)本质是将接口实现类的全限...
    99+
    2023-06-05
  • 解析Apache Dubbo的SPI实现机制
    目录一、SPI1.1、JDK自带SPI实现1.2、Dubbo SPI二、加载-ExtensionLoader2.1、获取ExtensionLoader的实例2.2、加载扩展类2.2....
    99+
    2024-04-02
  • Dubbo扩展点SPI实践示例解析
    目录正文扩展点配置:扩展实现类:拦截配置文件:调用拦截扩展:拦截扩展说明:常用约定:实现细节:扩展点的几个特点:扩展点自动包装扩展点自动装配扩展点自适应扩展点自动激活正文 Dubbo...
    99+
    2022-11-13
    Dubbo扩展点SPI实践 Dubbo SPI扩展
  • 浅析Java中的SPI原理
    在面向对象的程序设计中,模块之间交互采用接口编程,通常情况下调用方不需要知道被调用方的内部实现细节,因为一旦涉及到了具体实现,如果需要换一种实现就需要修改代码,这违反了程序设计的&q...
    99+
    2024-04-02
  • 基于JDK动态代理原理解析
    目录1、回顾一下JDK动态代理的核心参数2、实现一个简单的动态代理①、定义接口Subject②、定义接口的实现类RealSubject③、定义代理对象创建工厂④、创建测试类,为tar...
    99+
    2023-05-18
    JDK动态代理原理 JDK动态代理 动态代理原理
  • Dubbo源码解析之SPI(一):扩展类的加载过程
    Dubbo是一款开源的、高性能且轻量级的Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用、智能容错和负载均衡,以及服务自动注册和发现。Dubbo最早是阿里公司内部的RPC框架,于 2011 年开源,之后迅速成为国内该类开源...
    99+
    2023-06-05
  • 分析JDK的HashMap的原理
    这篇文章主要介绍“分析JDK的HashMap的原理”,在日常操作中,相信很多人在分析JDK的HashMap的原理问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”分析JDK的HashMap的原理”的疑惑有所帮助!...
    99+
    2023-06-02
  • MySQL系列连载之XtraBackup 备份原理解析
    目录MySQL 系列连载之 XtraBackup 备份原理(1)导读Xtrabackup简介原理1、MySQL主从同步原理2、XtraBackup备份原理3、备份的两个过程4、Xtr...
    99+
    2022-11-13
    mysql  XtraBackup 备份 mysql  XtraBackup mysql备份
  • MySQL系列连载之XtraBackup 备份原理解析
    目录mysql 系列连载之 XtraBackup 备份原理(1)导读Xtrabackup简介原理1、MySQL主从同步原理2、XtraBackup备份原理3、备份的两个过程4、XtraBackup的优点5、Xtrabac...
    99+
    2024-04-02
  • java开发Dubbo注解Adaptive实现原理
    目录前言什么是@Adaptive实现原理getAdaptiveExtensiongetAdaptiveExtensionClassgenerate前言 前面我们已经分析Dubbo S...
    99+
    2024-04-02
  • golang pprof 监控系列 go trace统计原理与使用解析
    目录引言go trace 使用统计原理介绍Goroutine analysisExecutionNetwork waitSync block,Blocking syscall,Sch...
    99+
    2023-05-14
    golang pprof监控go trace统计 golang pprof监控
  • Dubbo原理和机制详解(非常全面)
    Dubbo是一款Java RPC框架,致力于提供高性能的RPC远程服务调用方案。Dubbo 作为主流的微服务框架之一,为开发人员带来了非常多的便利。 本文我们重点详解 Dubbo 的原理机制 @mikechen 目录 Dubbo核心功能D...
    99+
    2023-09-12
    dubbo java 分布式 后端 架构
  • JDK基于CAS实现原子类盘点解析
    目录前言基础原子类原子引用原子数组原子字段更新器原子累加器总结前言 JDK中提供了一系列的基于CAS实现的原子类,CAS 的全称是Compare-And-Swap,底层是lock c...
    99+
    2022-12-08
    JDK CAS原子类 JDK CAS
  • Zookeeper原理及在Dubbo中的使用示例详解
    目录zookeeper原理用法配置管理命名服务分布式锁集群管理应用实例引入依赖配置 Dubbo 和 Zookeeper定义接口实现接口启动服务zookeeper Zookeeper...
    99+
    2023-03-19
    Zookeeper在Dubbo使用原理 Zookeeper Dubbo
  • Java实现JDK动态代理的原理详解
    目录概念案例静态代理JDK动态代理模式原理分析真相大白概念 代理:为控制A对象,而创建出新B对象,由B对象代替执行A对象所有操作,称之为代理。一个代理体系建立涉及到3个参与角色:真实...
    99+
    2024-04-02
  • 【Java 线程系列 】线程池原理解析--看这一篇就够了
    作者:半身风雪 上篇:阻塞队列原理解析 线程池原理解析 一、为什么要用线程池 二、ThreadPoolExecutor 的类关系 三、线程池的创建各个参数含义 ...
    99+
    2023-08-31
    java 开发语言 android
  • Spring中JDK和cglib动态代理原理的示例分析
    这篇文章给大家分享的是有关Spring中JDK和cglib动态代理原理的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。Java代理介绍Java中代理的实现一般分为三种:JDK静态代理、JDK动态代理以及C...
    99+
    2023-06-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作