千家信息网

什么是Dubbo SPI机制

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,本篇内容主要讲解"什么是Dubbo SPI机制",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"什么是Dubbo SPI机制"吧!SPI是什么SPI是一种简称
千家信息网最后更新 2026年11月01日什么是Dubbo SPI机制

本篇内容主要讲解"什么是Dubbo SPI机制",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"什么是Dubbo SPI机制"吧!

SPI是什么

SPI是一种简称,全名叫 Service Provider Interface,Java本身提供了一套SPI机制,SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类,这样可以在运行时,动态为接口替换实现类,这也是很多框架组件实现扩展功能的一种手段。

而今天要说的Dubbo SPI机制和Java SPI还是有一点区别的,Dubbo 并未使用 Java 原生的 SPI 机制,而是对他进行了改进增强,进而可以很容易地对Dubbo进行功能上的扩展。

学东西得带着问题去学,我们先提几个问题,再接着看

1.什么是SPI(开头已经解释了)

2.Dubbo SPI和Java原生的有什么区别

3.两种实现应该如何写出来

Java SPI是如何实现的

先定义一个接口:

public interface Car {  void startUp(); }

然后创建两个类,都实现这个Car接口

public class Truck implements Car{  @Override  public void startUp() {   System.out.println("The truck started");  } }  public class Train implements Car{  @Override  public void startUp() {   System.out.println("The train started");  } }

然后在项目META-INF/services文件夹下创建一个名称为接口的全限定名,com.example.demo.spi.Car。

文件内容写上实现类的全限定名,如下:

com.example.demo.spi.Train com.example.demo.spi.Truck

最后写一个测试代码:

public class JavaSPITest {  @Test  public void testCar() {   ServiceLoader serviceLoader = ServiceLoader.load(Car.class);   serviceLoader.forEach(Car::startUp);  } }

执行完的输出结果:

The train started The truck started

Dubbo SPI是如何实现的

Dubbo 使用的SPI并不是Java原生的,而是重新实现了一套,其主要逻辑都在ExtensionLoader类中,逻辑也不难,后面会稍带讲一下

看看使用,和Java的差不了太多,基于前面的例子来看下,接口类需要加上@SPI注解:

@SPI public interface Car {  void startUp(); }

实现类不需要改动

配置文件需要放在META-INF/dubbo下面,配置写法有些区别,直接看代码:

train = com.example.demo.spi.Train truck = com.example.demo.spi.Truck

最后就是测试类了,先看代码:

public class JavaSPITest {  @Test  public void testCar() {   ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);   Car car = extensionLoader.getExtension("train");   car.startUp();  } }

执行结果:

The train started

Dubbo SPI中常用的注解

  • @SPI 标记为扩展接口

  • @Adaptive自适应拓展实现类标志

  • @Activate 自动激活条件的标记

总结一下两者区别:

  • 使用上的区别Dubbo使用ExtensionLoader而不是ServiceLoader了,其主要逻辑都封装在这个类中

  • 配置文件存放目录不一样,Java的在META-INF/services,Dubbo在META-INF/dubbo,META-INF/dubbo/internal

  • Java SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,并且又用不上,会造成大量资源被浪费

  • Dubbo SPI 增加了对扩展点 IOC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点

  • Java SPI加载过程失败,扩展点的名称是拿不到的。比如:JDK 标准的 ScriptEngine,getName() 获取脚本类型的名称,如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因是不会有任何提示的,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因

前面的3个问题是不是已经能回答出来了?是不是非常简单

Dubbo SPI源码分析

Dubbo SPI使用上是通过ExtensionLoader的getExtensionLoader方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。这其中,getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,如果没有缓存,则创建一个新的实例,直接上代码:

public T getExtension(String name) {     if (name == null || name.length() == 0) {         throw new IllegalArgumentException("Extension name == null");     }     if ("true".equals(name)) {         // 获取默认的拓展实现类         return getDefaultExtension();     }     // 用于持有目标对象     Holder holder = cachedInstances.get(name);     if (holder == null) {         cachedInstances.putIfAbsent(name, new Holder());         holder = cachedInstances.get(name);     }     Object instance = holder.get();     // DCL     if (instance == null) {         synchronized (holder) {             instance = holder.get();             if (instance == null) {                 // 创建扩展实例                 instance = createExtension(name);                 // 设置实例到 holder 中                 holder.set(instance);             }         }     }     return (T) instance; }

上面这一段代码主要做的事情就是先检查缓存,缓存不存在创建扩展对象

接下来我们看看创建的过程:

private T createExtension(String name) {     // 从配置文件中加载所有的扩展类,可得到"配置项名称"到"配置类"的映射关系表     Class clazz = getExtensionClasses().get(name);     if (clazz == null) {         throw findException(name);     }     try {         T instance = (T) EXTENSION_INSTANCES.get(clazz);         if (instance == null) {             // 反射创建实例             EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());             instance = (T) EXTENSION_INSTANCES.get(clazz);         }         // 向实例中注入依赖         injectExtension(instance);         Set> wrapperClasses = cachedWrapperClasses;         if (wrapperClasses != null && !wrapperClasses.isEmpty()) {             // 循环创建 Wrapper 实例             for (Class wrapperClass : wrapperClasses) {                 // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。                 // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量                 instance = injectExtension(                     (T) wrapperClass.getConstructor(type).newInstance(instance));             }         }         return instance;     } catch (Throwable t) {         throw new IllegalStateException("Extension instance (name: " + name + ", class: " +                     type + ") couldn't be instantiated: " + t.getMessage(), t);     } }

这段代码看着繁琐,其实也不难,一共只做了4件事情:

1.通过getExtensionClasses获取所有配置扩展类

2.反射创建对象

3.给扩展类注入依赖

4.将扩展类对象包裹在对应的Wrapper对象里面

我们在通过名称获取扩展类之前,首先需要根据配置文件解析出扩展类名称到扩展类的映射关系表,之后再根据扩展项名称从映射关系表中取出相应的拓展类即可。相关过程的代码如下:

private Map> getExtensionClasses() {     // 从缓存中获取已加载的拓展类     Map> classes = cachedClasses.get();     // DCL     if (classes == null) {         synchronized (cachedClasses) {             classes = cachedClasses.get();             if (classes == null) {                 // 加载扩展类                 classes = loadExtensionClasses();                 cachedClasses.set(classes);             }         }     }     return classes; }

这里也是先检查缓存,若缓存没有,则通过一次双重锁检查缓存,判空。此时如果 classes 仍为 null,则通过 loadExtensionClasses 加载拓展类。下面是 loadExtensionClasses 方法的代码

private Map> loadExtensionClasses() {     // 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入的     final SPI defaultAnnotation = type.getAnnotation(SPI.class);     if (defaultAnnotation != null) {         String value = defaultAnnotation.value();         if ((value = value.trim()).length() > 0) {             // 对 SPI 注解内容进行切分             String[] names = NAME_SEPARATOR.split(value);             // 检测 SPI 注解内容是否合法,不合法则抛出异常             if (names.length > 1) {                 throw new IllegalStateException("more than 1 default extension name on extension...");             }              // 设置默认名称,参考 getDefaultExtension 方法             if (names.length == 1) {                 cachedDefaultName = names[0];             }         }     }      Map> extensionClasses = new HashMap>();     // 加载指定文件夹下的配置文件     loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);     loadDirectory(extensionClasses, DUBBO_DIRECTORY);     loadDirectory(extensionClasses, SERVICES_DIRECTORY);     return extensionClasses; }

loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件。SPI 注解解析过程比较简单,无需多说。下面我们来看一下 loadDirectory 做了哪些事情

private void loadDirectory(Map> extensionClasses, String dir) {     // fileName = 文件夹路径 + type 全限定名      String fileName = dir + type.getName();     try {         Enumeration urls;         ClassLoader classLoader = findClassLoader();         // 根据文件名加载所有的同名文件         if (classLoader != null) {             urls = classLoader.getResources(fileName);         } else {             urls = ClassLoader.getSystemResources(fileName);         }         if (urls != null) {             while (urls.hasMoreElements()) {                 java.net.URL resourceURL = urls.nextElement();                 // 加载资源                 loadResource(extensionClasses, classLoader, resourceURL);             }         }     } catch (Throwable t) {         logger.error("Exception occurred when loading extension class (interface: " +                     type + ", description file: " + fileName + ").", t);     } }

loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源。我们继续跟下去,看一下 loadResource 方法的实现

private void loadResource(Map> extensionClasses, ClassLoader classLoader,                           java.net.URL resourceURL) {     try {         BufferedReader reader = new BufferedReader(             new InputStreamReader(resourceURL.openStream(), "utf-8"));         try {             String line;             // 按行读取配置内容             while ((line = reader.readLine()) != null) {                 // 定位 # 字符                 final int ci = line.indexOf('#');                 if (ci >= 0) {                     // 截取 # 之前的字符串,# 之后的内容为注释,需要忽略                     line = line.substring(0, ci);                 }                 line = line.trim();                 if (line.length() > 0) {                     try {                         String name = null;                         int i = line.indexOf('=');                         if (i > 0) {                             // 以等于号 = 为界,截取键与值                             name = line.substring(0, i).trim();                             line = line.substring(i + 1).trim();                         }                         if (line.length() > 0) {                             // 加载类,并通过 loadClass 方法对类进行缓存                             loadClass(extensionClasses, resourceURL,                                        Class.forName(line, true, classLoader), name);                         }                     } catch (Throwable t) {                         IllegalStateException e =                           new IllegalStateException("Failed to load extension class...");                     }                 }             }         } finally {             reader.close();         }     } catch (Throwable t) {         logger.error("Exception when load extension class...");     } }

loadResource 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass 方法进行其他操作。loadClass 方法用于主要用于操作缓存,该方法的逻辑如下:

private void loadClass(Map> extensionClasses, java.net.URL resourceURL,      Class clazz, String name) throws NoSuchMethodException {          if (!type.isAssignableFrom(clazz)) {         throw new IllegalStateException("...");     }      // 检测目标类上是否有 Adaptive 注解     if (clazz.isAnnotationPresent(Adaptive.class)) {         if (cachedAdaptiveClass == null) {             // 设置 cachedAdaptiveClass缓存             cachedAdaptiveClass = clazz;         } else if (!cachedAdaptiveClass.equals(clazz)) {             throw new IllegalStateException("...");         }              // 检测 clazz 是否是 Wrapper 类型     } else if (isWrapperClass(clazz)) {         Set> wrappers = cachedWrapperClasses;         if (wrappers == null) {             cachedWrapperClasses = new ConcurrentHashSet>();             wrappers = cachedWrapperClasses;         }         // 存储 clazz 到 cachedWrapperClasses 缓存中         wrappers.add(clazz);              // 程序进入此分支,表明 clazz 是一个普通的拓展类     } else {         // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常         clazz.getConstructor();         if (name == null || name.length() == 0) {             // 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 name             name = findAnnotationName(clazz);             if (name.length() == 0) {                 throw new IllegalStateException("...");             }         }         // 切分 name         String[] names = NAME_SEPARATOR.split(name);         if (names != null && names.length > 0) {             Activate activate = clazz.getAnnotation(Activate.class);             if (activate != null) {                 // 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键,                 // 存储 name 到 Activate 注解对象的映射关系                 cachedActivates.put(names[0], activate);             }             for (String n : names) {                 if (!cachedNames.containsKey(clazz)) {                     // 存储 Class 到名称的映射关系                     cachedNames.put(clazz, n);                 }                 Class c = extensionClasses.get(n);                 if (c == null) {                     // 存储名称到 Class 的映射关系                     extensionClasses.put(n, clazz);                 } else if (c != clazz) {                     throw new IllegalStateException("...");                 }             }         }     } }

综上,loadClass方法操作了不同的缓存,比如cachedAdaptiveClass、cachedWrapperClasses和cachedNames等等

到这里基本上关于缓存类加载的过程就分析完了,其他逻辑不难,认真地读下来加上Debug一下都能看懂的。

到此,相信大家对"什么是Dubbo SPI机制"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

方法 文件 缓存 配置 实例 注解 名称 代码 内容 对象 接口 机制 过程 逻辑 事情 文件夹 资源 反射 存储 检测 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技有限公司 博彦科技互联网金融 广州为想互联网科技有限公司 亿人帮互联网科技有限公司 中国互联网科技工作委员会毛军 互联网科技行业发展 互联网金融公司科技 致骋互联网科技 睿民互联网科技 科技伦理 互联网公司 四个字互联网科技公司取名 南山科技园互联网公司有哪些 互联网软件和科技 链环科技传统企业怎么转型互联网 山东子淼互联网科技有限公司 上海钢联属于互联网科技股吗 互联网科技包装股份有限公司上市 扫呗互联网科技有限公司 福州德业互联网科技 杭州科技互联网公司有哪些 华服互联网科技靠谱吗 中关村互联网科技集团 科技互联网公司装修 易互联网科技有限公司 高科技与互联网 苏州互联网科技贷款公司 长亮科技互联网金融 银川新华互联网科技职业技术培训学校图片 国大互联网教育科技有限公司工作 深圳市罗太互联网科技有限公司
0