SPI服务发现机制

SPI是Java JDK内部提供的一种服务发现机制。

  • SPI->Service Provider Interface,服务提供接口,是Java JDK内置的一种服务发现机制

  • 通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类

[️注意事项]:

面向对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行编码。如果涉及实现类就会违反可插拔的原则,针对于模块装配,Java SPI提供了为某个接口寻找服务的实现机制。

SPI规范

  • 使用约定:

    [1].编写服务提供接口,可以是抽象接口和函数接口,JDK1.8之后推荐使用函数接口

[2].在jar包的META-INF/services/目录里创建一个以服务接口命名的文件。其实就是实现该服务接口的具体实现类。

提供一个目录:
META-INF/services/
放到ClassPath下面

[3].当外部程序装配这个模块的时候,就能通过该Jar包META-INF/services/配置文件找到具体的实现类名,并装载实例化,完成模块注入。

目录下放置一个配置文件:
文件名是需要拓展的接口全限定名称
文件内部为要实现的接口实现类
文件必须为UTF-8编码

[4].寻找服务接口实现,不用在代码中提供,而是利用JDK提供服务查找工具类:java.util.ServiceLoader类来加载使用:

ServiceLoader.load(xxx.class)
ServiceLoader<XXXInterface> loads = ServiceLoader.load(xxx.class)

SPI源码分析

[1].ServiceLoader源码:

package java.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.AccessController;
import java.security.AccessControlContext;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException; public final class ServiceLoader<S> implements Iterable<S>
{
//[1].初始化定义全局配置文件路径Path
private static final String PREFIX = "META-INF/services/";
//[2].初始化定义加载的服务类或接口
private final Class<S> service;
//[3].初始化定义类加载器
private final ClassLoader loader;
//[4].初始化定义访问控制上下文
private final AccessControlContext acc;
//[5].初始化定义加载服务类的缓存集合
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
//[6].初始化定义私有内部LazyIterator类,真正加载服务类的实现类
private LazyIterator lookupIterator; //私有化有参构造-> ServiceLoader(Class<S> svc, ClassLoader cl)
private ServiceLoader(Class<S> svc, ClassLoader cl) { //[1].实例化服务接口->Class<S>
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//[2].实例化类加载器->ClassLoader
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
//[3].实例化访问控制上下文->AccessControlContext
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
//[4].回调函数->reload
reload();
} public void reload() {
//[1].清空缓存实例集合
providers.clear();
//[2].实例化私有内部LazyIterator类->LazyIterator
lookupIterator = new LazyIterator(service, loader);
} public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
} public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
} }

2.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;
} private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
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);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
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
} public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action =
new PrivilegedAction<Boolean>() {
public Boolean run() {
return hasNextService();
}
};
return AccessController.doPrivileged(action, acc);
}
} public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action =
new PrivilegedAction<S>() {
public S run() {
return nextService();
}
};
return AccessController.doPrivileged(action, acc);
}
} public void remove() {
throw new UnsupportedOperationException();
}
}

使用举例

[1].Dubbo SPI 机制:

META-INF/dubbo.internal/xxx=接口全限定名

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。

Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。

与Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。

[2].Cache SPI 机制:

  META-INF/service/javax.cache.spi.CachingProvider=xxx

[3]Spring SPI 机制:

META-INF/services/org.apache.commons.logging.LogFactory=xxx

[4].SpringBoot SPI机制:

META-INF/spring.factories/org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx

在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回

源码:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式为:key=value1,value2,value3
// 从所有的jar包中找到META-INF/spring.factories文件
// 然后从文件中解析出key=factoryClass类名称的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 取得资源文件的URL
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 遍历所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 根据资源文件URL解析properties文件,得到对应的一组@Configuration类
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
// 组装数据,并返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}

[5].自定义序列化实现SPI:META-INF/services/xxx=接口全限定名

参考学习Java SPI 和Dubbo SPI机制源码,自己动手实现序列化工具类等

版权声明:本文为博主原创文章,遵循相关版权协议,如若转载或者分享请附上原文出处链接和链接来源。

Java编程技术之浅析SPI服务发现机制的更多相关文章

  1. Java编程技术之浅析JVM内存

    JVM JVM->Java Virtual Machine:Java虚拟机,是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的. 基本认知: ...

  2. Java编程技术之浅析Java容器技术

    Java容器 集合是一种存储数据的容器,是Java开发中使用最频繁的对象类型之一. 或许提起Collection,都会第一时间意识到List和Set以及Map等相关关键词.因为这几乎是我们日常开发里接 ...

  3. 10个实用的但偏执的Java编程技术

    在沉浸于编码一段时间以后,你会渐渐对这些东西习以为常.因为,你知道的-- 任何事情有可能出错,没错,的确如此. 这就是为什么我们要采用"防御性编程",即一些偏执习惯的原因.下面是我 ...

  4. 转载:10个实用的但偏执的Java编程技术

    在沉浸于编码一段时间以后(比如说我已经投入近20年左右的时间在程序上了),你会渐渐对这些东西习以为常.因为,你知道的…… 任何事情有可能出错,没错,的确如此. 这就是为什么我们要采用“防御性编程”,即 ...

  5. (转载)10个实用的但偏执的Java编程技术

    10个实用的但偏执的Java编程技术 在沉浸于编码一段时间以后(比如说我已经投入近20年左右的时间在程序上了),你会渐渐对这些东西习以为常.因为,你知道的…… 作者:小峰来源:码农网|2015-09- ...

  6. 深入理解SPI机制-服务发现机制

    https://www.jianshu.com/p/3a3edbcd8f24 SPI ,全称为 Service Provider Interface,是一种服务发现机制.它通过在ClassPath路径 ...

  7. 基于consul构建golang系统分布式服务发现机制

    原文地址-石匠的Blog: http://www.bugclosed.com/post/5 在分布式架构中,服务治理是一个重要的问题.在没有服务治理的分布式集群中,各个服务之间通过手工或者配置的方式进 ...

  8. Prometheus在Kubernetes下的服务发现机制

    Prometheus作为容器监控领域的事实标准,随着以Kubernetes为核心的云原生热潮的兴起,已经得到了广泛的应用部署.灵活的服务发现机制是Prometheus和Kubernetes两者得以连接 ...

  9. Java多线程技术:实现多用户服务端Socket通信

    目录 前言回顾 一.多用户服务器 二.使用线程池实现服务端多线程 1.单线程版本 2.多线程版本 三.多用户与服务端通信演示 四.多用户服务器完整代码 最后 前言回顾 在上一篇<Java多线程实 ...

随机推荐

  1. Windows无法访问共享文件夹

    问题描述 今天打开vss连接代码,提示如下信息 解决办法 可行:重置登录用户信息 原博文 https://zhidao.baidu.com/question/1174230805440255699.h ...

  2. (六)、mv-一个具备更改文件名和移动文件的命令

    一.mv的命令格式和作用 既可以在不同目录之间移动文件和目录,也可以在同一目录下对文件或者目录重命名,如果目标文件已经存在,则可覆盖他,若 目标文件或者目录不存在,则创建他:移动的dst必须为目录 命 ...

  3. 自己总结的关于图论的一些算法实现(C语言实现,有较详细注释,800行左右)

    1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #define TRUE 1 5 # ...

  4. 7.mysql8.0版本MGR搭建

    搭建MGR 1.配置文件 loose-group_replication_ip_whitelist = 192.168.124.0/24 loose-group_replication_start_o ...

  5. JAVA多线程下高并发的处理经验

    java中的线程:java中,每个线程都有一个调用栈存放在线程栈之中,一个java应用总是从main()函数开始运行,被称为主线程.一旦创建一个新的线程,就会产生一个线程栈.线程总体分为:用户线程和守 ...

  6. Keras使用多个GPU并行

    model = Model(inputs=[v_i, v_j], outputs=output_list) model = multi_gpu_model(model,4) model.compile ...

  7. [LeetCode98]98. Validate Binary Search Tree判断二叉搜索树

    判断二叉搜索树的方法是: 中序遍历形成递增序列 //全局变量记录中序遍历产生的序列,因为要递归,所以要用全局变量 List<Integer> list = new ArrayList< ...

  8. 使用Android Studio来阅读Android源码

    在编译android系统后,执行下面命令来生成索引. mmm development/tools/idegen/mv ./out/target/product/tiny4412/obj/GYP/sha ...

  9. Ubuntu+KVM显卡透传

    好久没有更新微博了,最近有点忙,大家见谅啊!今天带来的是我前段时间做的东西,也就是在 Ubuntu下做KVM虚拟机显科透传.(最近人有点懒,其实有几次是有时间更新的,但是就是懒得动,唉!得保持清醒不能 ...

  10. Kubernetes官方java客户端之三:外部应用

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...