SPI 全称为 (Service Provider Interface),是 JDK 内置的一种服务提供发现机制,能够轻松实现面向服务的注册与发现,实现服务提供与应用的解耦,并且能够实现动静加载
SPI 能做什么
利用 SPI 机制,sdk 的开发者能够为使用者提供扩大点,使用者无需批改源码,有点相似 Spring @ConditionalOnMissingBean 的意思
入手实现一个 SPI
例如咱们要正在开发一个 sdk 其中有一个缓存的性能,然而用户很可能不想应用咱们的缓存实现,用户想要自定义缓存的实现,此时应用 spi 就十分的适合了
新建一个 maven 工程命名为 sdk
Cache 接口
import java.util.ServiceLoader;
public interface Cache {String getName();
static Cache load() {
// ServiceLoader 实现了 Iterable,能够加载到 Cache 接口的多个实现类
ServiceLoader<Cache> cacheServiceLoader = ServiceLoader.load(Cache.class);
return cacheServiceLoader.iterator().next();
}
}
ServiceLoader 是 Java 提供服务发现工具类,这是咱们实现 SPI 的要害
CacheDefaultImpl
public class CacheDefaultImpl implements Cache {public String getName() {return "defaultImpl";}
}
除此之外,ServiceLoader 还须要在 classpath:META-INF/services
下找到 以该接口全名命名 的文件,这里咱们间接在 resource 目录下创立 META-INF/services/ com.github.tavenyin.Cache
文件即可,文件中指定 Cache 的实现类
# 此处能够指定多个实现类
com.github.tavenyin.CacheDefaultImpl
Run
咱们建设一个新的 maven 子工程,并引入 sdk 模块,执行测试代码
System.out.println(Cache.load().getName())
# 输入后果为 defaultImpl
使用者定制化
那么如果 sdk 的使用者不想应用咱们的 CacheDefaultImpl
了怎么办,没关系使用者只须要笼罩 classpath:META-INF/services/com.github.tavenyin.Cache
就能够了(使用者在同样在 resource 下创立即可笼罩)
咱们再来运行一下测试代码,输入后果为 newImpl
ServiceLoader 实现原理
ServiceLoader 的实现原理还是比较简单的,试想一下,如果咱们本人实现一个 ServiceLoader,咱们会怎么做?
- 通过指定的文件加载出所有的类名
- 通过反射构建这些对象
没错,ServiceLoader 就是这么做的,咱们来简略看一下源码
入口 ServiceLoader::iterator::next
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
// ServiceLoader::iterator
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();}
// ServiceLoader::iterator::next
public S next() {if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();}
public void remove() {throw new UnsupportedOperationException();
}
};
}
从 providers 初始为一个空的 LinkedHashMap,咱们无需关注,所以knownProviders::hasNext
肯定返回 false,咱们直奔 knownProviders::next
knownProviders::next 中外围逻辑在 nextService() 中
private S nextService() {
// hasNextService 中做了两件事
// 1. 判断是否还有服务的提供者
// 2. 通过 "META-INF/services/" + 接口全名 加载所有提供者 ClassName
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 通过 ClassName 创立 Class
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}
与咱们上述剖析的实现过程统一,更多细节感兴趣的童鞋可自行浏览
ServiceLoader 如何实现动静加载
同一个 ServiceLoader 对象的话,不会从新加载 META-INF/services/
下的信息。如果咱们须要动静加载的话,能够思考每次从新创立新的 ServiceLoader 对象,或者调用 ServiceLoader::reload
demo 地址
https://github.com/TavenYin/j…
如果感觉有播种,能够关注我的公众号【殷地理】,你的点赞和关注就是对我最大的反对