Java SPI 原理介绍
一.背景
最近的一个季度我们想要把我们项目中某个业务功能能够以最小的适配成本迁移到另一个项目当中去用,减少类似功能的重复开发。想要实现不同项目的都能低成本接入,就需要我们把该模块独立出来。但是该模块随着不断的需求迭代开发,在代码层级上不仅仅和项目其他业务代码存在耦合,而且也和很多58底层库存在代码耦合。如何解耦,让该业务更加干净成为一个核心问题,而我们这里使用的核心技术就是Java SPI。
二.介绍
SPI 全称为 Service Provider Interface, 字面意思: “服务提供者的接口”。是JDK 内置的动态加载实现的机制,将服务接口和具体的实现分离开来从而提升程序的扩展性。 可能SPI 还是比较模糊。我们说大家熟悉的API
模块之间都是通过接口进行调用。当服务方提供了接口和实现,我们通过调用服务方的接口从而调用服务功能,这种方式就是API。 接口和实现都在服务方。
当接口存在在调用方的时候,就是SPI. 由接口的调用方确定接口规则,然后由不同的实现方对这个接口进行实现,提供不同的服务。
本质上SPI 是 基于接口编程+策略模式+配置文件 组合动态加载机制。
三. 简单使用
- 新建一个module, 定义machine接口
package com.zyy.spi.machine
interface Machine {
fun powerOn()
}
- 在主项目app里面写几个代码实现
package com.zyy.spi.demo
import android.util.Log
import com.zyy.spi.machine.Machine
class Car : Machine {
override fun powerOn() {
Log.d("Machine", "Car powerOn")
}
}
package com.zyy.spi.demo
import android.util.Log
import com.zyy.spi.machine.Machine
class TV : Machine {
override fun powerOn() {
Log.d("Machine", "TV power on")
}
}
- 在module 中新建一个类使用,把所有机器开启
package com.zyy.spi.machine
import java.util.ServiceLoader
class MachineManager {
private val loader: ServiceLoader<Machine> = ServiceLoader.load(Machine::class.java)
val iterator: MutableIterator<Machine> = loader.iterator()
fun powerOn() {
while (iterator.hasNext()) {
val machine = iterator.next()
machine.powerOn()
}
}
}
- 在src/main(主工程和module目录都可以)下面resources/META_INF/services新建文件,文件名字为接口名称,文件里面填写接口具体实现类(注意一定是全限定名 包含包名)
- 找一个地方调用MachineManager#powerOn,可以看到成功开启了TV和Car
总结一下使用流程
- 定义接口
- 编写配置文件放到src/main/resources/META-INF/services/
- 使用ServiceLoader动态加载实现类(实现类必须是无参构造函数)
四. ServiceLoader源码分析
JDK-17版本源码
@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> var0) {
ClassLoader var1 = Thread.currentThread().getContextClassLoader();
return new ServiceLoader(Reflection.getCallerClass(), var0, var1);
}
private ServiceLoader(Class<?> var1, Class<S> var2, ClassLoader var3) {
Objects.requireNonNull(var2);
if (VM.isBooted()) {
checkCaller(var1, var2);
if (var3 == null) {
var3 = ClassLoader.getSystemClassLoader();
}
} else {
//....
}
this.service = var2;
this.serviceName = var2.getName();
this.layer = null;
this.loader = var3;
}
- 每一个接口都是单独的一个ServiceLoader,构造的时候保存接口名称
- 检查classloader是否准确
public Iterator<S> iterator() {
if (this.lookupIterator1 == null) {
this.lookupIterator1 = this.newLookupIterator(); //关键构造了newLookupIterator
}
return new Iterator<S>() {
public boolean hasNext() {
//....
// ServiceLoader.this.instantiatedProviders 是一个List缓存,没有则lookupIterator1获取
return this.index < ServiceLoader.this.instantiatedProviders.size() ? true
: ServiceLoader.this.lookupIterator1.hasNext();
}
public S next() {
//...
if (this.index < ServiceLoader.this.instantiatedProviders.size()) {
var1 = ServiceLoader.this.instantiatedProviders.get(this.index); //从缓存取
} else {
var1 = ((Provider)ServiceLoader.this.lookupIterator1.next()).get();// 关键从lookupIterator1中取
ServiceLoader.this.instantiatedProviders.add(var1);//添加到缓存中
}
++this.index;
return var1;
}
};
}
- 关键调用了this.newLookupIterator(),后续就是通过它进行class实现类的获取,初始化返回
- 全局有一个instantiatedProviders缓存,缓存了每次class实现类,缓存没有命中,则从lookupIterator1迭代器里面获取 下面我们看看lookupIterator1 的实现
private Iterator<Provider<S>> newLookupIterator() {
assert this.layer == null || this.loader == null;
if (this.layer != null) {
// 通过load方式不会走这里,layer一直为null,
//这个是ModuleLayer 主要用于将模块与ClassLoader映射,方便Java虚拟机知道类所属模块
//与Java关键字Module, 使用Java 9引入概念
return new LayerLookupIterator();
} else {
final ModuleServicesLookupIterator var1 = new ModuleServicesLookupIterator();//关注
final LazyClassPathLookupIterator var2 = new LazyClassPathLookupIterator(); //关注
return new Iterator<Provider<S>>() {
//....
}
};
}
}
我们重点关注一下LazyClassPathLookupIterator 的具体实现。ModuleServicesLookupIterator也是与ModuleLayer有关的,我们没有外部配置所以一直都是空Java 9的模块系统感兴趣可以看看
看LazyClassPathLookupIterator的具体实现
private final class LazyClassPathLookupIterator<T> implements Iterator<Provider<T>> {
static final String PREFIX = "META-INF/services/";
Set<String> providerNames = new HashSet();
Enumeration<URL> configs;
Iterator<String> pending;
Provider<T> nextProvider;
ServiceConfigurationError nextError;
LazyClassPathLookupIterator() {
}
//.....
public boolean hasNext() { //是否有下一个
if (ServiceLoader.this.acc == null) {
return this.hasNextService(); // 调用hasNextService
} else {
// ....
}
}
public Provider<T> next() {
if (ServiceLoader.this.acc == null) {
return this.nextService();
} else {
PrivilegedAction var1 = new PrivilegedAction<Provider<T>>() {
public Provider<T> run() {
return LazyClassPathLookupIterator.this.nextService();
}
};
return (Provider)AccessController.doPrivileged(var1, ServiceLoader.this.acc);
}
}
}
一共两个关键函数 1.一个hasNext → hasNextService → nextProviderClass
private Class<?> nextProviderClass() {
String var1;
if (this.configs == null) {
try {
//获取配置接口文件路径
var1 = "META-INF/services/" + ServiceLoader.this.service.getName();
if (ServiceLoader.this.loader == null) {
this.configs = ClassLoader.getSystemResources(var1);//
} else if (ServiceLoader.this.loader == ClassLoaders.platformClassLoader()) {
if (BootLoader.hasClassPath()) {
this.configs = BootLoader.findResources(var1);
} else {
this.configs = Collections.emptyEnumeration();
}
} else {
this.configs = ServiceLoader.this.loader.getResources(var1);
}
} catch (IOException var4) {
ServiceLoader.fail(ServiceLoader.this.service, "Error locating configuration files", var4);
}
}
//缓存,避免多次解析文件内容
while(this.pending == null || !this.pending.hasNext()) {
if (!this.configs.hasMoreElements()) {
return null;
}
//获取的值 file:/data/app/~~r9fySSE335YAH0IXyVIgOg==/com.zyy.spi.demo-K3RHHSS3I1zPcwNoHtgbtg==/base.apk!/META-INF/services/com.zyy.spi.machine.Machine
// parse(xxx) 就是一行一行获取文件内容
this.pending = this.parse((URL)this.configs.nextElement());
}
var1 = (String)this.pending.next();
try {
//获取实现类之后,通过Class.forName创建Class
return Class.forName(var1, false, ServiceLoader.this.loader);
} catch (ClassNotFoundException var3) {
ServiceLoader.fail(ServiceLoader.this.service, "Provider " + var1 + " not found");
return null;
}
}
2.next → nextService —> nextProviderClass 返回Class对象 —> ProviderImp, ProviderImp里面进行
private static class ProviderImpl<S> implements Provider<S> {
final Class<S> service;
final Class<? extends S> type;
final Method factoryMethod;
final Constructor<? extends S> ctor;
final AccessControlContext acc;
public S get() {
//factoryMethod 也是与Module联合使用,否则都是走newInstance方法
return this.factoryMethod != null ? this.invokeFactoryMethod() : this.newInstance();
}
private S invokeFactoryMethod() {
....
}
private S newInstance() {
Object var1 = null;
Throwable var2 = null;
if (this.acc == null) {
try {
var1 = this.ctor.newInstance(); //调用ctor 构造函数进行构造
} catch (Throwable var5) {
var2 = var5;
}
}
//.....
return var1;
}
}
其中简单看一下构造函数获取
private Constructor<?> getConstructor(final Class<?> var1) {
PrivilegedExceptionAction var2 = new PrivilegedExceptionAction<Constructor<?>>() {
public Constructor<?> run() throws Exception {
Constructor var1x = var1.getConstructor(); //获取无参构造函数
if (ServiceLoader.this.inExplicitModule(var1)) {
var1x.setAccessible(true);
}
return var1x;
}
};
...
}
整个步骤就很清晰了,核心实现逻辑在LazyClassPathLookupIterator
- 通过ClassLoader.getSystemResources 获取META-INF/services/下面某个服务的文件地址
- 然后读取文件内容每一行放到pending的数组里面
- 通过Class.forName进行返回对应的实现类
- 通过调用无参构造函数创建对象
- 已经加载实例对象保存到内存中
五.思考与扩展
SPI 其实是一种组件解耦的设计思想,ServiceLoader只是其中一种实现。 ServiceLoader目前也是存在一些问题
- 流程麻烦,每次添加一个具体实现类的时候,都需要更新META-INF/services/文件否则导致找不到该具体实现类,我们经常会忘记。 AutoService(github.com/google/auto…
- 使用时会同时加载所有的实现类,并缓存起来,无法实现真正的懒加载。
- 使用迭代器进行访问,不支持key 获取具体实现
- 线程不安全,ServiceLoader里面构造实现类非同步方法
转载自:https://juejin.cn/post/7366154691033088034