前言
Java SPI是一种用于实现解耦和扩展的机制,它使得开发者能够在不修改代码的情况下,动态地替换、添加或移除实现。通过Java SPI,我们可以将核心代码与具体实现解耦,实现高度可扩展的架构。本文将深入探索Java SPI的工作原理、使用方法和一些最佳实践
API和SPI区别
API是一组定义了软件组件之间交互方式的接口集合,通常由提供者自行开发。
SPI是一种软件设计模式,用于实现可插拔的组件扩展。
使用场景
- 扩展框架功能:SPI使得框架能够以插件的形式扩展功能。开发者可以实现特定的接口并提供相应的服务提供者,框架会在运行时动态地加载和调用这些服务提供者的实现。这样,框架的功能可以被灵活地扩展和定制,而不需要修改框架的核心代码。
- 应用程序配置:SPI允许应用程序在不修改代码的情况下,通过替换或添加服务提供者来改变应用程序的行为。这对于应用程序的配置和定制非常有用。例如,应用程序可能依赖于某种数据库,通过SPI,可以在配置中指定不同的数据库服务提供者,从而无需更改应用程序代码即可切换数据库。
- 插件化系统:SPI可用于构建插件化系统,使得开发者能够以插件的形式扩展系统的功能。系统定义一组接口,并提供SPI机制,插件开发者可以实现这些接口并提供相应的插件。系统在运行时会加载和管理这些插件,从而实现系统功能的动态扩展和定制。
- 模块化开发:SPI在模块化开发中也有广泛应用。模块可以定义一组服务接口,并通过SPI机制来获取实现这些接口的服务提供者。这样,模块之间可以实现解耦,每个模块只关注自己的功能实现,而不需要显式依赖其他模块的具体实现。
使用示例
首先定义一个接口Pay
, 这个接口有发起充值的功能,但是我现在并不确定有哪些第三方支付,变提供了第三方接口。
public interface Pay {
void pay();
}
public class WechatPay implements Pay {
@Override
public void pay() {
System.out.println("微信支付");
}
}
public class Alipay implements Pay{
@Override
public void pay() {
System.out.println("支付宝支付");
}
}
当实现好后,需要在src/main/resources/ 下建立 /META-INF/services 目录文件夹下新增一个文件,文件名为该接口的全限定名即:com.cementwork.test.demo.spi.Pay
, 内容为接口实现的全限定名。
com.cementwork.test.demo.spi.WechatPay
com.cementwork.test.demo.spi.Alipay
编写测试类
public static void main(String[] args) {
ServiceLoader<Pay> load = ServiceLoader.load(Pay.class);
Iterator<Pay> iterator = load.iterator();
while (iterator.hasNext()){
Pay next = iterator.next();
next.pay();
}
}
实现原理
核心在ServiceLoader这个类中
//扫描的前缀路径
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;
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//拼接要扫描的路径
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);
}
}
//循环扫描configs中所有的包路径
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
//pending缓存了所有查找到的类全限定名
nextName = pending.next();
return true;
}
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
}