深入理解java和dubbo的SPI机制 | 京东物流技术团队

京东云开发者
• 阅读 408

1 SPI简介

1.1 SPI(Service Provider Interface)

本质:将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

java SPI:用来设计给服务提供商做插件使用的。基于策略模式来实现动态加载的机制。我们在程序只定义一个接口,具体的实现交个不同的服务提供者;在程序启动的时候,读取配置文件,由配置确定要调用哪一个实现。

dubbo SPI:在dubbo中也有SPI机制,虽然都需要将接口全限定名配置在文件中,但是dubbo并没有使用java的spi机制,而是重新实现了一套功能更强的 SPI 机制, 支持了AOP与依赖注入,并且 利用缓存提高加载实现类的性能,同时 支持实现类的灵活获取。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。例如dubbo当中的protocol,LoadBalance等都是通过SPI机制扩展。

2 java SPI

2.1实现过程

1)需要在 classpath 下创建一个目录,该目录命名必须是:META-INF/service
2)在该目录下创建一个 文本文件,该文件需要满足以下几个条件

  • 文件名必须是扩展的接口的全路径名称
  • 文件内部描述的是该扩展接口的所有实现类
  • 文件的编码格式是 UTF-8

3)通过 java.util.ServiceLoader 的加载机制来加载服务

深入理解java和dubbo的SPI机制 | 京东物流技术团队

深入理解java和dubbo的SPI机制 | 京东物流技术团队

2.2 工作原理

1)当调用 ServiceLoader.load(Class clz) 方法时,会到jar中中的目录 “META-INF/services/“ + clz.getName 进行文件读取,

2)当在调用ServiceLoader.forEach()方法时,实际走的是LazyIterator,当在调用LazyIterator.hasNext() 时,在文件中读取到实际的服务实现类并把它们通过调用 Class.forName(String name, boolean initialize,ClassLoader loader)。

深入理解java和dubbo的SPI机制 | 京东物流技术团队

2.3 实际应用

javaSPI我们最熟悉的应用就是数据库驱动了,mysql和oracle驱动针对JDBC分别有自己的实现,这就有赖于java的SPI机制。

深入理解java和dubbo的SPI机制 | 京东物流技术团队

3 dubbo SPI

3.1 实现过程

1)需要在 classpath 下创建一个目录,该目录命名可以是:META-INF/service/、META-INF/dubbo/、META-INF/dubbo/internal/

2)在该目录下创建一个 文本文件,该文件需要满足以下几个条件

  • 文件名必须是扩展的接口的全路径名称
  • 文件内部描述的是该扩展接口的所有实现类,将服务实现类写成KV键值对的形式,Key是拓展类的name,Value是扩展的全限定名实现类。

3)通过 org.apache.dubbo.common.extension.ExtensionLoader 的加载机制来加载服务

深入理解java和dubbo的SPI机制 | 京东物流技术团队

深入理解java和dubbo的SPI机制 | 京东物流技术团队

3.2 工作原理

1)我们首先通过 ExtensionLoader的 getExtensionLoader 方法获取一个接口的 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象,源码如下,首先是 getExtensionLoader 方法:

深入理解java和dubbo的SPI机制 | 京东物流技术团队

new ExtensionLoader(type)源码如下:

深入理解java和dubbo的SPI机制 | 京东物流技术团队

注意这里创建 ExtensionLoader对象的构造方法如下:ExtensionLoader.getExtensionLoader获取ExtensionFactory接口的拓展类,再通过 getAdaptiveExtension从拓展类中获取目标拓展类。

2)通过 ExtensionLoader.getExtensionLoader取到接口的加载器Loader之后,再通过 getExtension方法获取需要拓展类对象。

深入理解java和dubbo的SPI机制 | 京东物流技术团队

以上代码首先检查holder中的实例缓存,缓存未命中则创建拓展对象。dubbo中包含了大量的扩展点缓存。这个就是典型的使用空间换时间的做法。

深入理解java和dubbo的SPI机制 | 京东物流技术团队

创建拓展类对象步骤分别为:

  1. 通过 getExtensionClasses 从配置文件中加载所有的拓展类,再通过名称获取目标拓展类
  2. 通过反射创建拓展对象
  3. 向拓展对象中注入依赖
  4. 将拓展对象包裹在相应的 Wrapper 对象中

我们接下来重点看下getExtensionClasses方法:

深入理解java和dubbo的SPI机制 | 京东物流技术团队

先从缓存中获取class,缓存未命中则调用loadExtensionClasses方法加载,我们再看下loadExtensionClasses这个方法:

深入理解java和dubbo的SPI机制 | 京东物流技术团队

我们看到这里遍历调用了多个策略去加载class的,跟到这里我们发现非常有意思的是:dubbo在加载META-INF目录下的class键值对的时候采用了javaSPI的方式

深入理解java和dubbo的SPI机制 | 京东物流技术团队

深入理解java和dubbo的SPI机制 | 京东物流技术团队

这里dubbo使用javaSPI的方式加载到3中类加载策略:

org.apache.dubbo.common.extension.DubboInternalLoadingStrategy 用于加载META-INF/dubbo/internal/中的class
org.apache.dubbo.common.extension.DubboLoadingStrategy 用于加载META-INF/dubbo/中的class
org.apache.dubbo.common.extension.ServicesLoadingStrategy 用于加载META-INF/service/中的class
dubbo的SPI还提供了自适应(Adaptive)、自动注入的功能就不在这里过多展开了,有兴趣可以自行了解。

3.3 实际应用

dubbo中大量使用了SPI机制:

深入理解java和dubbo的SPI机制 | 京东物流技术团队

例如dubbo的多协议的实现:

深入理解java和dubbo的SPI机制 | 京东物流技术团队

4 javaSPI和dubboSPI对比

  1. Java SPI在加载扩展点的时候,会一次性加载所有可用的扩展点,很多是不需要的,会浪费系统资源。dubboSPI有选择性地加载所需要的SPI接口。
  2. javaSPI配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。而dubboSPI配置文件中以键值对的形式有别名,易于区分。
  3. SPI扩展如果依赖其他的扩展,javaspi做不到自动注入和装配,dubbo可以实现自动注入。
  4. javaSPI不提供类似于Spring的IOC和AOP功能,dubboSPI是支持的

作者:京东物流 龚航林

来源:京东云开发者社区 自猿其说Tech 转载请注明来源

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
SPI应用场景及详解
java中spi(serviceproviderinterface)是jdk内置的一种服务发现机制,可以基于配置,在运行时加载指定服务。java中提供了很多服务提供接口,如jdbc、jndi等。面对分布式的开发,很多系统之间的调用都是使用rpc直接调用,但是有的时候上游的系统需要调用下游系统很多的接口,导致开发工作量很大。因此上游系统使用sp
可插拔组件设计机制—SPI
SPI的全称是ServiceProviderInterface,即提供服务接口;是一种服务发现机制,SPI的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。本篇文章聚焦SPI的使用场景及使用介绍。
Stella981 Stella981
3年前
Dubbo 扩展点加载机制:从 Java SPI 到 Dubbo SPI
!(https://oscimg.oschina.net/oscnet/up1aa4ada0efc8a144d35d25b3443d951c7e3.JPEG)SPI全称为ServiceProviderInterface,是一种服务发现机制。当程序运行调用接口时,会根据配置文件或默认规则信息加载对应的实现类。所以在程序中并没有直接指定使用接口
Easter79 Easter79
3年前
Spring的两种代理JDK和CGLIB的区别浅谈
一、原理区别:java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以
Stella981 Stella981
3年前
Dubbo实践(七)扩展点
与JDK的SPI机制类似,Dubbo也在METAINF路径下定义了多种扩展接口。只是JDKSPI机制是Java后台帮你实现读取文件并对接具体的实现类,而Dubbo是自己去读文件。扩展点配置扩展点机制有几个要点:1. 根据关键字去读取配置文件,获得具体的实现类比如在dubboprovider.xml文件中配置:<dub
Stella981 Stella981
3年前
SPI 在 Dubbo中 的应用
通过本文的学习,可以了解DubboSPI的特性及实现原理,希望对大家的开发设计有一定的启发性。一、概述SPI全称为ServiceProviderInterface,是一种模块间组件相互引用的机制。其方案通常是提供方将接口实现类的全名配置在classPath下的指定文件中,由调用方读取并加载。这样需要替换某个组件时,只需要引入新
Stella981 Stella981
3年前
JVM 之 类的加载与初始化
JVM中的类或接口的加载,连接,初始化动作都是动态的。加载主要是根据指定的名称找到并读取类或接口的二进制表示形式,然后创建类和接口。连接则是通过验证,准备,解析等动作将相关联的类或接口合并为运行时形态以便可以被JVM执行。初始化的过程主要包括类或接口初始化方法<clinit的执行。JVM在启动时,首先会加载和创建"启动类",该类由具体JVM实现
Wesley13 Wesley13
3年前
Java类加载机制的理解
算上大学,尽管接触Java已经有4年时间并对基本的API算得上熟练应用,但是依旧觉得自己对于Java的特性依然是一知半解。要成为优秀的Java开发人员,需要深入了解Java平台的工作方式,其中类加载机制和JVM字节码这样的核心特性。今天我将记录一下我在新的学习路程中对Java类加载机制的理解。1.类加载机制类加载是一个将类合并到正在运
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
浅析Java - SPI机制 | 京东云技术团队
SPI是什么SPI全称ServiceProviderInterface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。整体机制如下图JavaSPI实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制