Java SPI 机制详解
约 1103 字大约 4 分钟
Java SPI 机制详解
何谓 SPI?
SPI(Service Provider Interface)是一种服务提供者接口,旨在将服务接口与具体的实现分离,解耦服务的调用方和服务实现者。它允许在运行时动态加载并选择服务实现,提高程序的扩展性和可维护性。SPI 机制广泛应用于框架如 Spring、JDBC 驱动、日志系统(例如 SLF4J)等。
SPI 和 API 的区别
- API:指的是服务接口和实现方提供的功能接口,调用方直接调用这些接口实现。
- SPI:由服务调用方定义接口规范,服务提供方根据规范实现接口,服务调用方不直接依赖具体实现,而是通过 SPI 机制动态加载实现类。
实战演示
在实践中,SLF4J 是一个日志门面,依赖 SPI 机制来动态加载不同的日志实现(如 Logback、Log4j 等)。通过修改项目的依赖(例如 Maven 配置),即可切换日志实现,而无需修改项目代码。
项目结构
service-provider-interface
项目:定义了服务接口Logger
。LoggerService
类:利用ServiceLoader
加载服务实现。Main
类:作为服务调用方,利用LoggerService
调用日志服务。
public interface Logger {
void info(String msg);
void debug(String msg);
}
public class LoggerService {
private static final LoggerService SERVICE = new LoggerService();
private final Logger logger;
private final List<Logger> loggerList;
private LoggerService() {
ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
List<Logger> list = new ArrayList<>();
for (Logger log : loader) {
list.add(log);
}
loggerList = list;
logger = list.isEmpty() ? null : list.get(0);
}
public static LoggerService getService() {
return SERVICE;
}
public void info(String msg) {
if (logger == null) {
System.out.println("info 中没有发现 Logger 服务提供者");
} else {
logger.info(msg);
}
}
public void debug(String msg) {
if (loggerList.isEmpty()) {
System.out.println("debug 中没有发现 Logger 服务提供者");
}
loggerList.forEach(log -> log.debug(msg));
}
}
服务提供者实现
Logback
类:实现了Logger
接口,提供日志记录功能。- 在
META-INF/services/edu.jiangxuan.up.spi.Logger
文件中指定实现类。
public class Logback implements Logger {
@Override
public void info(String s) {
System.out.println("Logback info 打印日志:" + s);
}
@Override
public void debug(String s) {
System.out.println("Logback debug 打印日志:" + s);
}
}
使用 ServiceLoader
加载服务
ServiceLoader
是 JDK 提供的工具类,允许在运行时加载接口的实现类。它会通过读取 META-INF/services/
目录下的配置文件来查找服务实现类。
public final class ServiceLoader<S> implements Iterable<S> {
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public Iterator<S> iterator() {
return new Iterator<S>() {
// Implementation of iterating over service providers
};
}
}
工作原理
- 加载服务提供者:
ServiceLoader
会在META-INF/services
目录下查找与接口同名的文件,文件内容是实现类的全名。 - 创建服务实例:通过反射加载实现类,并生成实例。
- 动态选择实现:根据需要,
ServiceLoader
可以加载并实例化多个服务提供者,并通过迭代器访问。
自定义 ServiceLoader
实现
public class MyServiceLoader<S> {
private final Class<S> service;
private final List<S> providers = new ArrayList<>();
private final ClassLoader classLoader;
public static <S> MyServiceLoader<S> load(Class<S> service) {
return new MyServiceLoader<>(service);
}
private MyServiceLoader(Class<S> service) {
this.service = service;
this.classLoader = Thread.currentThread().getContextClassLoader();
doLoad();
}
private void doLoad() {
try {
Enumeration<URL> urls = classLoader.getResources("META-INF/services/" + service.getName());
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
URLConnection urlConnection = url.openConnection();
urlConnection.setUseCaches(false);
InputStream inputStream = urlConnection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String className = bufferedReader.readLine();
while (className != null) {
Class<?> clazz = Class.forName(className, false, classLoader);
if (service.isAssignableFrom(clazz)) {
Constructor<? extends S> constructor = (Constructor<? extends S>) clazz.getConstructor();
S instance = constructor.newInstance();
providers.add(instance);
}
className = bufferedReader.readLine();
}
}
} catch (Exception e) {
System.out.println("读取文件异常。。。");
}
}
public List<S> getProviders() {
return providers;
}
}
通过自定义 MyServiceLoader
,可以更加灵活地实现服务加载和实例化的逻辑,适应不同的需求。
总结
- SPI 通过解耦服务的调用方与实现方,实现了动态加载服务的能力,使得扩展更加灵活。
ServiceLoader
是 SPI 机制的核心工具,它通过读取配置文件、反射加载服务实现,实现了服务的自动发现与加载。- SPI 机制广泛应用于日志框架、数据库驱动、框架扩展等领域,具有很高的实用性。