Java SPI源码解析
1. SPI是什么,有什么用处
SPI全称S而vice Provider Interface,是java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
系统设计的各个抽象,一般有很多不同的实现方案,比如通过不同类型的配置文件加载配置信息,通过不同的序列化方案实现序列化。一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可插拔的原则。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
SPI的核心思想就是解耦。
2.使用场景
调用者根据实际需要替换框架的实现策略。
比如常见的例子:
- 数据库驱动加载类接口实现类的加载,JDBC加载不同类型数据库的驱动
- 日志实现类加载
- dubbo中也大量使用SPI的方式实现框架的扩展,不过它对java提供的SPI进行了封装
3.如何使用
实例代码
- 定义一组接口,并写出多个实现类
package com.djl.test.spi.api;
/**
* @ClassName Robot
* @Description TODO
* @Author djl
* @Date 2020-03-18 19:01
* @Version 1.0
*/
public interface Robot {
void sayHello();
}
package com.djl.test.spi.java;
import com.djl.test.spi.api.Robot;
/**
* @ClassName Bumblebee
* @Description TODO
* @Author djl
* @Date 2020-03-18 19:03
* @Version 1.0
*/
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("Hello ,I am Bumble bee");
}
}
package com.djl.test.spi.java;
import com.djl.test.spi.api.Robot;
/**
* @ClassName OptimusPrime
* @Description TODO
* @Author djl
* @Date 2020-03-18 19:02
* @Version 1.0
*/
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("Hello,I am Optimus prime");
}
}
- 在META-INF/services文件下,创建一个以接口全限定名命名的文件,内容为实现类的全限定名
com.djl.test.spi.java.Bumblebee
com.djl.test.spi.java.OptimusPrime
- 通过ServiceLoader类进行加载
import com.djl.test.spi.api.Robot;
import com.djl.test.spi.api.RobotDubbo;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.junit.Test;
import java.util.ServiceLoader;
/**
* @ClassName JavaSPITest
* @Description TODO
* @Author djl
* @Date 2020-03-18 19:06
* @Version 1.0
*/
public class JavaSPITest {
@Test
public void sayHello(){
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
System.out.println("java spi");
serviceLoader.forEach(Robot::sayHello);
}
}
- 执行结果
4 源码解析
我会将分析都写在代码注释中,大家可以打开自己的源码耐心的看一会。
接下来是重头戏了,知道了spi怎么用,那么内部是如何实现的呢?
我们直接从ServiceLoader类的load方法看起。
/**为给定的服务类型创建一个新的服务加载程序,使用
*当前线程的{@linkplain java.lang.Thread#getContextClassLoader
*上下文类装入器}。
*/
public static <S> ServiceLoader<S> load(Class<S> service) {
//1.获取当前线程的类加载
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
//new一个serviceloader对象
return new ServiceLoader<>(service, loader);
}
//构造函数
private ServiceLoader(Class<S> svc, ClassLoader cl) {
//判断入参是否为null
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//2.加载器如果不存在,获取系统类加载器,通常是applicationLoader,应用程序加载器
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
//3.获取访问控制器
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
// 清空缓存
providers.clear();
// 初始化内部类,用于遍历提供者
lookupIterator = new LazyIterator(service, loader);
}
看到这里,相信大家对于初始化的内容有了一定了解,这里面涉及到了一些属性,我们来总结下
private static final String PREFIX = "META-INF/services/";
// 要加载的类
private final Class<S> service;
// 用于加载实现类的类加载器
private final ClassLoader loader;
// 访问控制器
private final AccessControlContext acc;
// 提供者的缓存
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 一个内部类,用于遍历实现类
private LazyIterator lookupIterator;
现在我们发现重点就在于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 {
//获取META-INF/services下文件全称
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 Iterator<String> parse(Class<?> service, URL u)
throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
//存储配置文件中实现类的全限定名
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
//读取文件内容,这里不多说了,正常的流操作
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
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
}
//这里是iterable循环遍历
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
到这里整个链路就分析完成了。
感兴趣的小伙伴可以按照demo,自己跑一遍,有问题欢迎提问。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。