首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…
一 . 前言
虽然是一个老概念 , 但是都怪自己的轻微强迫症 , 有个点没搞清楚 , 就非要把这个概念翻出来看看, 这一篇是对 Java SPI 概念的一个完善 , 为后续的 Dubbo 等框架的分析做准备
Java SPI 是 JDK提供的SPI(Service Provider Interface)机制 , SPI 机制的核心在于 ServiceLoader , 用于加载接口对应的实现类
用一句话来解释 , 就是以 Java 的方式查找接口对应的实现类 , 实现解耦 (区别于 Spring 里面的Bean工具 , SPI 的方式更加底层)
二 . 知识点
SPI 机制中有四个组成部分 :
- Service Providers : 服务器供应商
- Installing Service Providers : 安装服务供应商
- Loading Service providers : 装载服务供应商
- Service Loader : 服务承载程式
Service Provider : 服务提供者 Service Provider 是 SPI 的特定实现。服务提供者包含一个或多个实现或扩展服务类型的具体类 , 服务提供者是通过我们放在资源目录 META-INF/services 中的提供者配置文件来配置和标识的
ServiceLoader ServiceLoader 是 SPI 的核心类 , 它的作用是发现和惰性加载实现 , 它使用上下文类路径来定位提供程序实现并将它们放在内部缓存中。
常用的 SPI 类
- CurrencyNameProvider : 为currency类提供本地化的货币符号
- LocaleNameProvider : 为Locale类提供本地化名称
- TimeZoneNameProvider : 为TimeZone类提供本地化的时区名称
- DateFormatProvider : 提供指定区域的日期和时间格式
- NumberFormatProvider : 为NumberFormat类提供货币值、整数和百分比值.
- Driver : 从4.0版本开始,JDBC API支持SPI模式
- PersistenceProvider : 提供JPA API的实现。
- JsonProvider : 提供JSON处理对象
- JsonbProvider : 提供JSON绑定对象
- Extension : 为CDI容器提供扩展
- ConfigSourceProvider : 提供用于检索配置属性的源
三 . SPI 的使用
SPI 的使用中我分成了三个包 :
// provider-api : API 描述包 , 包含 Provider 接口
|- ExchangeRateProvider : Provider 接口
|- QuoteManager : 一个需要我们通过 SPI 构建的业务接口
|- ProviderManager : Provider 管理器 , 加载 Provider 实现类
//provider-impl : 接口实现类 , 也是我们最终需要 Loader 出的类
|- YahooFinanceExchangeRateProvider
|- YahooQuoteManagerImpl
//server-application : 业务包 , 业务处理 , 获得 API 类
3.1 provider-api 包
// Step 1 : Provider 接口 , 我们用它返回一个通用的业务类
public interface ExchangeRateProvider {
QuoteManager create();
}
// Step 2 : 这个是我们的业务类
public interface QuoteManager {
List<String> getQuotes(String baseCurrency, LocalDate date);
}
// Step 3 : Provider 管理工具 , 加载出 Provider
public class ProviderManager {
private Logger logger = LoggerFactory.getLogger(this.getClass());
public Iterator<ExchangeRateProvider> providers(boolean refresh) {
logger.info("------> [Step 1 : 进入 Provider 处理流程] <-------");
ServiceLoader<ExchangeRateProvider> loader = ServiceLoader.load(ExchangeRateProvider.class);
if (refresh) {
loader.reload();
}
Iterator<ExchangeRateProvider> provider = loader.iterator();
return provider;
}
}
3.2 provider-impl
// 实现类
public class YahooFinanceExchangeRateProvider implements ExchangeRateProvider {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public QuoteManager create() {
logger.info("------> this is create <-------");
return new YahooQuoteManagerImpl();
}
}
public class YahooQuoteManagerImpl implements QuoteManager {
@Override
public List<String> getQuotes(String baseCurrency, LocalDate date) {
return new ArrayList<>();
}
}
3.3 Server applicaiton
public class StartService implements ApplicationRunner {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void run(ApplicationArguments args) throws Exception {
logger.info("------> [App 中获取] <-------");
ProviderManager providerManager = new ProviderManager();
Iterator<ExchangeRateProvider> providers = providerManager.providers(true);
while (providers.hasNext()) {
logger.info("------> [providers 获取完成 :{}] <-------", providers.next().create());
}
}
}
PS : 这里有个很重要的东西 , 你需要 META-INF/services 中添加对应的文件
- 文件名 : Provider 接口
- 文件内容 : 涉及到的实现类
这个文件放在 impl 和 app 中都行 , 实际上引包了就会扫描
<dependency>
<groupId>com.gang.study</groupId>
<artifactId>provider-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.gang.study</groupId>
<artifactId>provider-impl</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
PS : 内容已提交到 Git , 欢迎 Star !!!! 👉 Case/java/spi
四 . SPI 源码深入
如果就这么结束当然不符合我一贯的做法 , 源码还是要看一下的, 别说 , 还真有一些启发
4.1 运行的走向
// Step 1 : 发起 Providers 加载操作
Iterator<ExchangeRateProvider> providers = providerManager.providers(true);
// Step 2 : ServiceLoader 执行加载
ServiceLoader<ExchangeRateProvider> loader = ServiceLoader.load(ExchangeRateProvider.class);
// Step 3 : ServiceLoader 构造器
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();
}
// Step 4 : reload 加载
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
4.2 属性
// 默认从 META-INF/services/ 路径下加载
private static final String PREFIX = "META-INF/services/";
// 表示正在加载的服务的类或接口
private final Class<S> service;
// 类加载器用于定位、加载和实例化提供程序
private final ClassLoader loader;
// 创建ServiceLoader时获取的访问控制上下文
private final AccessControlContext acc;
// 缓存的提供商,按实例化顺序
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 惰性查找迭代器 , 最终获取会通过这个定制的迭代器
private LazyIterator lookupIterator;
4.3 resource 的加载
这里的核心是做了一个定制的实现类 LazyIterator
前面看了 ServiceLoader 的构建 , 这里来看一下 resource 的加载
// 这里不需要深入太多 , 主要是资源加载处理 Service 类
C- ServiceLoader
public Iterator<S> iterator() {
// 核心一 : 对迭代器做了简单的实现 , 用来调用定制的迭代器 LazyIterator
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext()){
return true;
}
// --> 最终调用 hasNextService()
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext()){
return knownProviders.next().getValue();
}
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
// 判断是否存在下一个
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// META-INF/services/java.util.spi.ResourceBundleControlProvider
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 通过 classLoader 加载 resource
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());
}
// 获得实现类的名称 com.gang.spi.demo.service.YahooFinanceExchangeRateProvider
nextName = pending.next();
return true;
}
// 读取 Resource 中资源
private Iterator<String> parse(Class<?> service, URL u)throws ServiceConfigurationError{
InputStream in = null;
BufferedReader r = null;
// 加载 SPI impl l
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
// Stream 逐行加载
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
// .... 省略 close
}
return names.iterator();
}
4.4 load 处理加载 Class
// 迭代器迭代
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
// 实例化 ProviderImpl
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 通过 cn 获取对象的 class 类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
// new ServiceConfigurationError(service.getName() + ": " + msg)
fail(service,"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,"Provider " + cn + " not a subtype");
}
try {
// 实例化 Service : com.gang.spi.api.service.ExchangeRateProvider
S p = service.cast(c.newInstance());
// cn : com.gang.spi.demo.service.YahooFinanceExchangeRateProvider
// p : com.gang.spi.demo.service.YahooFinanceExchangeRateProvider
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,"Provider " + cn + " could not be instantiated",x);
}
throw new Error(); // This cannot happen
}
PS: 核心就2个 ,一个是hasNext 中加载 resource , 再在 nextService 中实例化对应的server
这样的好处是 , 只有在正在使用的时候 , 才会真的去实例化这个对象 !!!
五 . 定制与比较
这里主要对比 Spring SPI 的加载方式 , 详见这一篇 盘点 SpringBoot : Factories 处理流程
文件的配置方面 : Spring 中使用的是 SpringFactoriesLoader , 其实与 Java 的方式是很像的 , 但是 Spring 的模式下 , 允许一个 factories 文件装载更多的类 , 使用更加简单 .
资源的加载方法 : 2 者都是通过 classLoader 加载 Resource , 并没有太大本质的区别
而资源的实例化方面 : Spring 通过一个 instantiateFactory 方法触发 ,但是同样的 , 也是 class 反射的原理
高并发情况 : 在调用 hasNext 的时候 , 加载 resource , 在迭代时才实例化 看起来好像没有什么问题 , 但是其 classLoader , provider 都是放在 ServiceLoader 对象属性中 , 多线程情况下会存在冲突
而 Spring 的模式 , 在 Server 启动时 , 就加载对象 Factories , 相对安全很多.
总得来说 , Spring Factories 的 Java SPI 的逻辑思路是一致的 , Java SPI 通过 LazyIterator 加载的方式比较骚气 ,但是相对而言获取起来就会很复杂 .
所以 , 完全可以使用 Spring Factories 来完成你自己想要的业务.
总结
定制 Iterator 完成业务是一个不错的思路