SPI的全称是Service Provider Interface,即服务提供商接口。直白的说,它主要用来实现一个可扩展的Java应用。有人会觉得这就是建立在面向接口编程下的一种为了使组件可扩展或动态变更实现的规范,常见的类SPI的设计有JDBC、JNDI、JAXP等,很多开源框架的内部实现也采用了SPI,。例如JDBC的架构是由一套API组成,用于给Java应用提供访问不同数据库的能力,而数据库提供商的驱动软件各不相同,JDBC通过提供一套通用行为的API接口,底层可以由提供商自由实现,虽然JDBC的设计没有指明是SPI,但也和SPI的设计类似。这里有兴趣的读者可以参看2002年的一篇老文章:Replaceable Components and the Service Provider Interface
JDK为SPI的实现提供了工具类,即java.util.ServiceLoader,ServiceLoader中定义的SPI规范没有什么特别之处,只需要有一个提供者配置文件(provider-configuration file),该文件需要在resource目录META-INF/services下,文件名就是服务接口的全限定名。文件内容是提供者Class的全限定名列表,显然提供者Class都应该实现服务接口。文件必须使用UTF-8编码。
动嘴不如动手,直接结合例子理解起来更简单,先新建我们服务接口类,该接口只有一个方法,那就是获取提供者名称:
package com.manzhizhen.study.spi;
/**
* SPI服务接口
*/
public interface SpiService {
/**
* 获取提供商名称
* @return 提供商名称
*/
String getProviderName();
}
这里我们假设有两家服务提供商,StandardSpiService和MzzSpiService,他们当然都需要实现SpiService接口,代码如下:
package com.manzhizhen.study.spi;
public class StandardSpiService implements SpiService {
@Override
public String getProviderName() {
return "This is StandardSpiService";
}
}
package com.manzhizhen.study.spi;
public class MzzSpiService implements SpiService {
@Override
public String getProviderName() {
return "This is MzzSpiService";
}
}
万事俱备,现在我们可以创建服务提供者文件了,在META-INF/services下新建名称为com.manzhizhen.study.spi.SpiService的服务提供者文件,因为目前只有两家提供商,所以该文件内容如下:
com.manzhizhen.study.spi.StandardSpiService
com.manzhizhen.study.spi.MzzSpiService
现在,我们写一个main方法来乐呵(测试)一下:
public static void main(String[] args) {
ServiceLoader<SpiService> spiServiceLoader = ServiceLoader.load(SpiService.class);
while(true) {
for (SpiService spiService : spiServiceLoader) {
System.out.println(spiService.getProviderName());
}
// 过段时间修改com.manzhizhen.study.spi.SpiService文件看是否能做到动态增减SPI的实现
try {
Thread.sleep(1000);
// 为了验证动态加载功能,这里每隔一秒都重新reload
spiServiceLoader.reload();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行该main方法后,我们可以看到控制台输出如下:
This is StandardSpiService
This is MzzSpiService
This is StandardSpiService
This is MzzSpiService
...
然后我们删除com.manzhizhen.study.spi.SpiService文件的第二行(即MzzSpiService提供商),结果输出变了:
This is StandardSpiService
This is MzzSpiService
This is StandardSpiService
This is MzzSpiService
This is StandardSpiService
This is StandardSpiService
This is StandardSpiService
...
有兴趣的读者可以看ServiceLoader内部的实现,其实整个流程就包含如下几步:
1.读取服务提供配置文件。
2.newInstance实例化配置文件中列举的服务提供类,所以服务提供类需要有默认的构造方法。
3.将实例化的服务提供类存储起来,即LinkedHashMap<String,S>providers=newLinkedHashMap<>()。
可见,SPI并没有什么神奇的地方,只不过是一种通过面向接口编程来实现服务透明变更的规范而已,带来的好处自然是我们业务系统变得扩展性更强。
接下来我们看看Dubbo中利用SPI做了哪些事情。Dubbo中SPI进行了扩展,对服务提供者配置文件中的内容进行了改造,由原来的提供者类的全限定名列表改成了KV形式的列表,这也导致了Dubbo中无法直接使用ServiceLoader,所以,与之对应的,在Dubbo中有ExtensionLoader,ExtensionLoader是扩展点载入器,用于载入Dubbo中的各种可配置组件,比如动态代理方式(ProxyFactory)、负载均衡策略(LoadBalance)、RCP协议(Protocol)、拦截器(Filter)、容器类型(Container)、集群方式(Cluster)和注册中心类型(RegistryFactory)等,总之,Dubbo为了应对各种场景,它的所有内部组件都是通过这种SPI的方式来管理的,这也是为什么Dubbo需要将服务提供者配置文件设计成KV键值对形式,这个K就是我们在Dubbo配置文件或注解中用到的K,Dubbo直接通过服务接口(上面提到的ProxyFactory、LoadBalance、Protocol、Filter等)和配置的K从ExtensionLoader拿到服务提供的实现类。与ServiceLoader的load方法对应的是ExtensionLoader的getExtensionLoader方法:
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if(!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if(!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
其中的EXTENSION_LOADERS的定义如下:
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
可见,不同的服务接口类型都在EXTENSION_LOADERS中有对应一个ExtensionLoader对象,这样能方便的管理不同服务接口的扩展点。当得到对应服务接口的ExtensionLoader后,就直接通过服务提供配置文件中的K来拿对应的服务提供者实现类的实例了(通过ExtensionLoader#),所以,在Dubbo的源码中随处可见如下代码:
ExtensionLoader.getExtensionLoader(Container.class).getExtension("spring");
ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(RoundRobinLoadBalance.NAME);
当然,对于提供者配置文件来说,是可以重复配置的,只是多配置的会被忽略掉,这里ServiceLoader和ExtensionLoader采取的策略是类似的。
看到这里,我们已经明白Dubbo框架内部是如何采用SPI来组装自己的功能的,那么如果某些扩展点是由使用方(即Dubbo的用户)来定义的,Dubbo是怎么加载它们的呢?前面说了ServiceLoader中默认的服务提供者配置文件的目录是MEAT-INF/services/,该目录由ServiceLoader的PREFIX来定义,而Dubbo中则会在三种目录下去加载服务提供者配置文件,由ExtensionLoader的三个成员变量来定义:
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
即对于每个CLASS PATH,Dubbo都会去扫描资源文件夹下的这三个目录来加载扩展点,加载过程可以参看ExtensionLoader#ExtensionLoader:
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if(defaultAnnotation != null) {
String value = defaultAnnotation.value();
if(value != null && (value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if(names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if(names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
可以看到最后几行就是去这三个目录下去找服务提供者配置文件,loadFile的方法是通用的,因为很多开源框架都有资源查找的需求,比如说Spring,所以loadFile的实现这里就不给出了。从这个方法也可以看出,Dubbo中的所有服务接口都标有@SPI注解,这样就能轻易的区分出该接口下是否会有服务提供类。但Dubbo是怎么保证扫描到所有包含服务提供者配置文件的呢?因为我们知道,在Spring容器中,如果它需要扫描包含@Service注解的实现类时,它需要我们去指导Spring去哪些CLASS PATH路径下面找这些需要注册的服务Bean,但Dubbo好像并不需要我们显式的告诉它去哪些地方找这些服务提供者配置文件。后来才发现,在ClassLoader中已经把它所加载的class和其对应的package信息(packages)、对应的文件系统路径(pdcache)都存储了起来,由于我们实现Dubbo的服务接口来自定义Dubbo扩展点时,我们需要依赖Dubbo的包,所以业务类的类加载器只会是Dubbo容器的父类加载器或者是同一个类加载器,这样就能很容易找到所有的CLASS PATH了(双亲委派模型),所以Dubbo也是在所有的CLASS PATH下去查找这三个文件。
<!--StartFragment--> <!--EndFragment-->
可以看出,Java体系的SPI还是大有用途,是一种面向接口编程的经典案例。
相关推荐
【Dubbo源代码(2.5.4)】是一份重要的开源项目资源,它包含了Dubbo框架在2.5.4版本的完整源代码。Dubbo是中国阿里巴巴公司贡献的高性能、轻量级的服务治理框架,它专注于服务调用、监控和服务治理。这个版本的源...
【描述】"git下载,调整pom文件,通过mvn install -Dmaven.test.skip=true打包" 这段描述揭示了获取和构建Dubbo源代码的过程。首先,使用Git工具从官方仓库克隆源代码,这涉及到Git的基本操作如`git clone`。其次,...
Dubbo采用SPI(Service Provider Interface)机制,允许用户自定义实现各种扩展点。`ExtensionLoader`类是SPI的核心,负责加载和管理扩展。开发者可以根据需要扩展`Protocol`、`Registry`、`Filter`等接口。 总结,...
本项目是基于Dubbo的学习实践,包含了作者自己编写的源代码,分为三个部分:`dubbo-demo-api`、`dubbo-demo-provider_api`和`dubbo-demo-consumer_api`,分别代表了服务接口定义、服务提供者和服务消费者。...
- Dubbo与Spring深度集成,源码中可以看到如何通过Spring的Bean管理和服务注入来实现服务的生命周期管理和依赖注入。 通过深入研究dubbo-2.5.4的源码,开发者不仅可以掌握Dubbo的工作原理,还能学习到分布式系统...
在【压缩包子文件的文件名称列表】"apache-dubbo-3.1.4-src"中,你可以找到dubbo的主要模块和源代码,包括: 1. `dubbo-common`:包含了dubbo的核心通用模块,如配置管理、日志处理、序列化等。 2. `dubbo-remoting...
- **6.3.2.1 扩展点配置**:在配置文件中定义扩展点,指定具体的实现类。 - **6.3.2.2 扩展点加载流程**:根据配置信息加载扩展点实现类。 - **6.3.2.3 扩展点装饰**:通过装饰模式增强扩展点的功能。 - **6.3....
在这样的工程中,开发者通常会复用和理解Dubbo源码中的ExtensionLoader工作原理,通过编写自己的代码来实现类似的功能,以支持自定义的扩展点和插件。 ExtensionLoader在Dubbo中的主要功能包括: 1. **加载器机制*...
读者可以通过Git命令克隆最新的Dubbo源代码,使用的构建工具是Maven。在构建之前,需要配置Java环境,并设置MAVEN_OPTS环境变量,分配足够的内存给Maven。构建过程中,可以使用命令跳过单元测试,以加快构建速度。...
通过下载并分析dubbo2.5.8的源代码,开发者可以更好地了解服务治理、远程调用、集群容错等核心模块的实现,这对于提升Java后端开发能力,尤其是分布式系统设计和微服务架构的理解非常有益。 【标签】"java dubbox...
7. **SPI扩展机制**:Dubbo采用Java的SPI(Service Provider Interface)来实现插件化,允许用户自定义实现各种扩展点。源码解析将介绍`META-INF/services`目录下的配置文件以及`...
Dubbo的SPI允许开发者自定义服务消费者、服务提供者、协议、序列化方式等组件,只需在相应的扩展点定义自己的实现,然后在`META-INF/dubbo`目录下创建对应的配置文件。这样,Dubbo在运行时会自动加载这些扩展,使得...
实现细节部分会深入探讨 Dubbo 源码中的具体实现方式,包括但不限于扩展点的加载机制、内部通信协议的实现、服务的发布与引用流程、负载均衡策略、容错机制等。例如,在动态代理扩展中,Dubbo 提供了 JDK 动态代理和...
### Dubbo学习手册知识点概述 #### 一、背景与简介 **电商系统演变历程:** 1. **单一应用框架(ORM)阶段:** - **特点:** 将所有功能如下单、支付等功能部署在一起,简化数据库操作。 - **问题:** 随着业务...
10. **扩展点(Extension Point)**:Dubbo采用SPI(Service Provider Interface)设计,允许用户自定义实现各种组件,如协议、序列化、注册中心等。 通过学习和研究这个压缩包中的内容,开发者不仅可以了解Dubbo的...
本文将深入探讨`incubator-dubbo-ops-master.zip`这个压缩包中的内容,重点解析Dubbo的运维平台(Dubbo Operations)的设计理念和实现细节。 1. **Dubbo 运维平台概述** Apache Dubbo-ops 是Dubbo项目的运维控制台...
9. **SPI扩展机制**:Dubbo通过Service Provider Interface (SPI)机制,允许用户自定义实现Dubbo的各个组件,如协议、序列化方式、负载均衡算法等。 通过深入学习和实践Dubbo Demo,开发者不仅能了解Dubbo的基础...
6. **SPI扩展**:服务提供者和消费者可以通过SPI机制自定义扩展点,增强框架的功能。 综上所述,这个“dubbo-project”可能是为了学习、实践或演示如何使用Dubbo来构建分布式服务的项目,涉及Java编程、服务注册与...
这个源码包包含了完整的源代码,可供开发者进行深度学习和二次开发。通过直接将源码包mvn install到本地maven仓库,开发者可以轻松地在自己的项目中引用和使用。 1. **Dubbo概述** Dubbo是一款高性能、轻量级的...
如果你在尝试编译最新版的Dubbox源代码时遇到问题,可以直接使用这个预编译的JAR包,以避免编译错误并快速集成到你的项目中。 Dubbox的主要特点和关键组件包括: 1. **服务治理**:Dubbo提供了一套完整的服务注册...