「周末福报」你了解 SPI 吗?
洛克李:如果没办法连续做500下伏地挺身的话,就要跳绳1200次!如果不能连续跳绳1200下的话,就要踢木头2000次!
正文开始
SPI(Service Provider Interface)是一种 JDK 提供的服务发现机制。
它通过读取指定( Classpath 路径下的 META/services)目录下(以服务命名)的文件,来自动加载文件中的定义类。
例如:
我们在对数据库进行操作时,经常会使用到 java.sql.Driver 接口。然而,数据库厂商们提供的协议都不一样,提供的 java.sql.Driver 实现也不同。
程序员在使用 java.sql.Driver 进行开发时,并不知道用户最终会使用哪个数据库。
这时候,就可以使用 Java 提供的 SPI 机制,来寻找 java.sql.Driver 接口对应的实现类。
示例代码
首先创建一个日志接口类,用来模拟日志打印功能。
package cn.live.service;
public interface Log {
void log(String msg);
}
提供 Log4j、LogBack 两个实现类,代表两种日志框架的不同实现。
public class Log4j implements Log {
@Override
public void log(String msg) {
System.out.println(String.format("Log4j: %s", msg));
}
}
public class LogBack implements Log {
@Override
public void log(String msg) {
System.out.println(String.format("LogBack: %s", msg));
}
新建 META-INF/services 目录,并添加一个 cn.live.service.Log 文件。
cn.live.service.Log 文件内容为该接口实现类文件的全路径。
cn.live.service.impl.LogBack
cn.live.service.impl.Log4j
新建一个测试类,进行验证。
public class LogTest {
public static void main(String[] args) {
ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
Iterator<Log> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
Log log = iterator.next();
log.log("JDK SPI");
}
}
}
控制台输出了 LogBack、Log4j 的不同实现信息。
以上,通过简单的打印日志示例代码,展示了 SPI 机制的简单运用。
JDK SPI 源代码
使用当前线程的 ClassLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
首先,会检查传入的接口类是否存在。
其次,当前线程的 ClassLoader 不存在时,默认使用 SystemClassLoader。
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
首先,清理 LinkedHashMap 缓存。
其次,初始化新的 LazyIterator 对象,实现完全延迟的查找。
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
LazyIterator 是私有的内部类,实现完全延迟的提供程序查找。
- hasNextService 会读取固定前缀 PREFIX 服务接口文件数据,并将读到的数据赋值给 pending 对象。
- nextService 会将 hasNextService 方法读取到的信息,转换成实现类 Class,赋值给 providers 对象。
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();
}
}
小结
Java 很多知识点,说明白之后。其实非常的简单。
就是要不停的去回顾,再学习。
如果这周没有再去回顾 SPI,那么突然问我 “你了解 SPI 吗?简单介绍一下 SPI,原理是什么?有哪些经典的运用?”
我:一脸懵逼
(一首《凉凉》送给自己)
这个周末,又一次成功“强迫”自己学习。
感谢各位小伙伴的阅读,这里是一个技术人的学习与分享。
- Spark详解03Job 物理执行图Job 物理执行图
- 干货|Kotlin入门第一课:从对比Java开始
- Spark详解04Shuffle 过程Shuffle 过程
- Spark详解02Job 逻辑执行图Job 逻辑执行图
- Spark详解01概览|Spark部署|执行原理概览Job 例子
- Spark详解05架构Architecture架构
- SQL Server常用命令(平时不用别忘了)
- Spark详解06容错机制Cache 和 Checkpoint Cache 和 Checkpoint
- SQL Server 学习笔记
- Collaborative Filtering(协同过滤)算法详解
- 【Hadoop】三句话告诉你 mapreduce 中MAP进程的数量怎么控制?
- Spark系列课程-00xxSpark RDD持久化
- RDD持久化
- P02_Hadoop CDH 5.3.6集群搭建
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- php+Ajax处理xml与json格式数据的方法示例
- python3 循环读取excel文件并写入json操作
- PHP根据key删除数组中指定的元素
- PHP JWT初识及其简单示例
- PHP正则判断一个变量是否为正整数的方法
- php5.5使用PHPMailer-5.2发送邮件的完整步骤
- 详细对比php中类继承和接口继承
- 解决Keras的自定义lambda层去reshape张量时model保存出错问题
- 解决Keras中Embedding层masking与Concatenate层不可调和的问题
- 浅谈keras使用预训练模型vgg16分类,损失和准确度不变
- 关于tf.matmul() 和tf.multiply() 的区别说明
- python中执行smtplib失败的处理方法
- PHP+Ajax简单get验证操作示例
- Python matplotlib读取excel数据并用for循环画多个子图subplot操作
- python转化excel数字日期为标准日期操作