一、什么是SPI
SPI全称Service Provider Interface,是Java提供的一种服务发现机制。实现服务接口和服务实现的解耦。
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,实现不修改任何代码的情况下切换不同的实现。
二、使用场景
很多开源第三方jar包都有基于SPI的实现,在jar包META-INF/services中都有相关配置文件。
如下几个常见的场景:
1)JDBC加载不同类型的数据库驱动
2)Slf4j日志框架
3)Dubbo框架
三、使用步骤示例
假设有个上传附件的场景,可以上传到不同的云储存(如阿里云OSS,亚马逊S3),那么基于Java SPI机制的实现,我们应该做如下步骤:
步骤1、创建4个工程
SPI的核心就是实现服务接口和服务实现的解耦,所以我们不能将接口和实现放在一个工程里面。
- spi-file-upload,在这定义附件上传接口
IFileUpload
- spi-file-upload-oss,实现附件上传到oss,
FileUploadOss
实现接口IFileUpload
- spi-file-upload-s3,实现附件上传到s3,
FileUploadS3
实现接口IFileUpload
- spi-file-upload-test,通过ServiceLoader加载接口实现,进行测试
步骤2、 在工程spi-file-upload创建接口IFileUpload
接口代码示例
package com.hj.test.file.oss;
import com.hj.test.file.IFileUpload;
public class FileUploadOss implements IFileUpload {
@Override
public void upload(String fileName) {
System.out.println("上传到阿里云OSS..." + fileName);
}
}
步骤3、分别创建接口实现类FileUploadOss、FileUploadS3
1)FileUploadOss
在工程的 spi-file-upload-oss 的 resources目录下创建目录META-INF/services,并在该目录中创建以接口IFileUpload全路径命名的文件(com.hj.test.file.IFileUpload),文件内容是接口实现类 com.hj.test.file.oss.FileUploadOss
package com.hj.test.file.oss;
import com.hj.test.file.IFileUpload;
public class FileUploadOss implements IFileUpload {
@Override
public void upload(String fileName) {
System.out.println("上传到阿里云OSS..." + fileName);
}
}
2)FileUploadS3
在工程的 spi-file-upload-s3 的 resources目录下创建目录META-INF/services,并在该目录中创建以接口IFileUpload全路径命名的文件(com.hj.test.file.IFileUpload),文件内容是接口实现类 com.hj.test.file.s3.FileUploadS3
package com.hj.test.file.s3;
import com.hj.test.file.IFileUpload;
public class FileUploadS3 implements IFileUpload {
@Override
public void upload(String fileName) {
System.out.println("上传到亚马逊s3..." + fileName);
}
}
步骤4、在工程spi-file-upload-test中创建测试调用类
1)在pom.xml中引入3个依赖工程
<dependency>
<groupId>com.hj</groupId>
<artifactId>spi-file-upload</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.hj</groupId>
<artifactId>spi-file-upload-oss</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.hj</groupId>
<artifactId>spi-file-upload-s3</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
2)测试实现
package com.hj.test.file.test;
import com.hj.test.file.IFileUpload;
import java.util.Iterator;
import java.util.ServiceLoader;
public class FileTest{
public static void main(String[] args) {
ServiceLoader<IFileUpload> loader = ServiceLoader.load(IFileUpload.class);
for(Iterator<IFileUpload> it = loader.iterator(); it.hasNext();){
IFileUpload file = it.next();
file.upload("测试文件上传");
}
}
}
控制台输出
上传到阿里云OSS...测试文件上传
上传到亚马逊s3...测试文件上传
如果哪天不想要传到s3,只需要把jar包依赖去掉就可以,无需改代码
四、原理解析
1、SPI的核心就是ServiceLoader.load()
方法
总结如下:
- 调用
ServiceLoader.load()
,创建一个ServiceLoader
实例对象 - 创建
LazyIterator
实例对象lookupIterator
- 通过
lookupIterator.hasNextService()
方法读取固定目录META-INF/services/
下面service全限定名文件,放在Enumeration
对象configs
中 - 解析configs得到迭代器对象
Iterator<String> pending
- 通过
lookupIterator.nextService()
方法初始化读取到的实现类,通过Class.forName()
初始化
从上面的步骤可以总结以下几点
- 实现类工程必须创建定目录
META-INF/services/
,并创建service全限定名文件,文件内容是实现类全限定名 - 实现类必须有一个无参构造函数
2、ServiceLoader核心代码介绍
public final class ServiceLoader<S>
implements Iterable<S>
{
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(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();
}
通过方法iterator()生成迭代器,内部调用LazyIterator实例对象
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
内部类LazyIterator,读取配置文件META-INF/services/
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();
}
}