Java编程技术之浅析SPI服务发现机制
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服务发现机制的更多相关文章
- Java编程技术之浅析JVM内存
JVM JVM->Java Virtual Machine:Java虚拟机,是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的. 基本认知: ...
- Java编程技术之浅析Java容器技术
Java容器 集合是一种存储数据的容器,是Java开发中使用最频繁的对象类型之一. 或许提起Collection,都会第一时间意识到List和Set以及Map等相关关键词.因为这几乎是我们日常开发里接 ...
- 10个实用的但偏执的Java编程技术
在沉浸于编码一段时间以后,你会渐渐对这些东西习以为常.因为,你知道的-- 任何事情有可能出错,没错,的确如此. 这就是为什么我们要采用"防御性编程",即一些偏执习惯的原因.下面是我 ...
- 转载:10个实用的但偏执的Java编程技术
在沉浸于编码一段时间以后(比如说我已经投入近20年左右的时间在程序上了),你会渐渐对这些东西习以为常.因为,你知道的…… 任何事情有可能出错,没错,的确如此. 这就是为什么我们要采用“防御性编程”,即 ...
- (转载)10个实用的但偏执的Java编程技术
10个实用的但偏执的Java编程技术 在沉浸于编码一段时间以后(比如说我已经投入近20年左右的时间在程序上了),你会渐渐对这些东西习以为常.因为,你知道的…… 作者:小峰来源:码农网|2015-09- ...
- 深入理解SPI机制-服务发现机制
https://www.jianshu.com/p/3a3edbcd8f24 SPI ,全称为 Service Provider Interface,是一种服务发现机制.它通过在ClassPath路径 ...
- 基于consul构建golang系统分布式服务发现机制
原文地址-石匠的Blog: http://www.bugclosed.com/post/5 在分布式架构中,服务治理是一个重要的问题.在没有服务治理的分布式集群中,各个服务之间通过手工或者配置的方式进 ...
- Prometheus在Kubernetes下的服务发现机制
Prometheus作为容器监控领域的事实标准,随着以Kubernetes为核心的云原生热潮的兴起,已经得到了广泛的应用部署.灵活的服务发现机制是Prometheus和Kubernetes两者得以连接 ...
- Java多线程技术:实现多用户服务端Socket通信
目录 前言回顾 一.多用户服务器 二.使用线程池实现服务端多线程 1.单线程版本 2.多线程版本 三.多用户与服务端通信演示 四.多用户服务器完整代码 最后 前言回顾 在上一篇<Java多线程实 ...
随机推荐
- CentOS7部署GeoServer
CentOS7部署GeoServer 一.安装JDK81.下载jdk1.8 wget http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a ...
- PPT技术干货1(上)——设计审美
序言 PPT直接反映了一个人的能力和态度,PPT能直接反映出老板最看重的4个关键能力: 逻辑思维:全局思考,洞察关键 数据思维:数据分析,指导决策 设计思维:美观大方,彰显专业 工作效率:效率高,出活 ...
- 使用xshell软件进行文件的上传和下载
1.选择xshell的文件里面的属性-->文件传输,把上传路径和下载路径设置好. 上传路径:介绍我们需要向Linux系统里面传东西. 下载路径:就是我们把Linux系统里面的大小拷贝出来. 2. ...
- 前端可视化开发--liveload
在前端开发中,我们会频繁的修改html.css.js,然后刷新页面,开效果,再调整,再刷新,不知不觉会浪费掉我们很多时间.有没有什么方法,我在编辑器里面改了代码以后,只要保存,浏览器就能实时刷新.经过 ...
- Redis 设计与实现 5:压缩列表
压缩列表是 ZSET.HASH和 LIST 类型的其中一种编码的底层实现,是由一系列特殊编码的连续内存块组成的顺序型数据结构,其目的是节省内存. ziplist 的结构 外层结构 下图展示了压缩列表的 ...
- 开发规范(三)数据库 By 阿里
建表规约 索引规约 SQL语句 ORM映射
- springMVC搭建分布式框架
https://www.cnblogs.com/lr393993507/p/7652717.html https://www.cnblogs.com/Tpf386/p/10987931.html
- Linux性能优化:CPU性能分析工具--vmstat
Blog:博客园 个人 目录 参数说明 输出信息说明 procs memory swap io system cpu 示例 vmstat是Virtual Meomory Statistics(虚拟内存 ...
- 「译」使用 System.Net.Http.Json 高效处理Json
在这篇文章,我将介绍一个名为 System.Net.Http.Json 的扩展库,它最近添加到了 .NET 中,我们看一下这个库能够给我们解决什么问题,今天会介绍下如何在代码中使用. 在此之前我们是如 ...
- ssh 免密设置
在master中生成dsa: ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa cat ~/.ssh/id_dsa.pub >> ~/.ssh/author ...