// 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();
}
};
}
SPI 能做什么
利用SPI机制,sdk的开发者可以为使用者提供扩展点,使用者无需修改源码,有点类似Spring @ConditionalOnMissingBean 的意思
动手实现一个SPI
例如我们要正在开发一个sdk其中有一个缓存的功能,但是用户很可能不想使用我们的缓存实现,用户想要自定义缓存的实现,此时使用spi就非常的合适了
新建一个maven工程命名为sdk
Cache 接口
CacheDefaultImpl
除此之外,ServiceLoader 还需要在
classpath:META-INF/services
下找到以该接口全名命名的文件,这里我们直接在resource 目录下创建META-INF/services/ com.github.tavenyin.Cache
文件即可,文件中指定Cache的实现类Run
我们建立一个新的maven子工程,并引入sdk模块,执行测试代码
使用者定制化
那么如果sdk的使用者不想使用我们的
CacheDefaultImpl
了怎么办,没关系使用者只需要覆盖classpath:META-INF/services/com.github.tavenyin.Cache
就可以了 (使用者在同样在resource下创建即可覆盖)我们再来运行一下测试代码,输出结果为
newImpl
ServiceLoader 实现原理
ServiceLoader 的实现原理还是比较简单的,试想一下,如果我们自己实现一个ServiceLoader,我们会怎么做?
没错,ServiceLoader 就是这么做的,我们来简单看一下源码
入口 ServiceLoader::iterator::next
从providers 初始为一个空的LinkedHashMap,我们无需关注,所以
knownProviders::hasNext
一定返回false,我们直奔knownProviders::nextknownProviders::next 中核心逻辑在nextService() 中
与我们上述分析的实现过程一致,更多细节感兴趣的童鞋可自行阅读
ServiceLoader 如何实现动态加载
同一个 ServiceLoader 对象的话,不会重新加载
META-INF/services/
下的信息。如果我们需要动态加载的话,可以考虑每次重新创建新的ServiceLoader 对象,或者调用 ServiceLoader::reloaddemo 地址
https://github.com/TavenYin/java-spi.git