SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
❞
本文主要是特性 & 用法介绍,不涉及源码解析(源码都很简单,相信你一定一看就懂)
SPI 有什么用?
举个栗子,现在我们设计了一款全新的日志框架:「super-logger」。默认以XML文件作为我们这款日志的配置文件,并设计了一个配置文件解析的接口:
package com.github.kongwu.spisamples;
public interface SuperLoggerConfiguration {
void configure(String configFile);
}
然后来一个默认的XML实现:
package com.github.kongwu.spisamples;
public class XMLConfiguration implements SuperLoggerConfiguration{
public void configure(String configFile){
......
}
}
那么我们在初始化,解析配置时,只需要调用这个XMLConfiguration来解析XML配置文件即可。
package com.github.kongwu.spisamples;
public class LoggerFactory {
static {
SuperLoggerConfiguration configuration = new XMLConfiguration();
configuration.configure(configFile);
}
public static getLogger(Class clazz){
......
}
}
这样就完成了一个基础的模型,看起来也没什么问题。不过扩展性不太好,因为如果想定制/扩展/重写解析功能的话,我还得重新定义入口的代码,LoggerFactory 也得重写,不够灵活,侵入性太强了。
比如现在用户/使用方想增加一个 yml 文件的方式,作为日志配置文件,那么只需要新建一个YAMLConfiguration,实现 SuperLoggerConfiguration 就可以。但是……怎么注入呢,怎么让 LoggerFactory中使用新建的这个 YAMLConfiguration ?难不成连 LoggerFactory 也重写了?
如果借助SPI机制的话,这个事情就很简单了,可以很方便的完成这个入口的扩展功能。
下面就先来看看,利用JDK 的 SPI 机制怎么解决上面的扩展性问题。
JDK SPI
JDK 中 提供了一个 SPI 的功能,核心类是 java.util.ServiceLoader。其作用就是,可以通过类名获取在"META-INF/services/"下的多个配置实现文件。
为了解决上面的扩展问题,现在我们在META-INF/services/
下创建一个com.github.kongwu.spisamples.SuperLoggerConfiguration
文件(没有后缀)。文件中只有一行代码,那就是我们默认的com.github.kongwu.spisamples.XMLConfiguration
(注意,一个文件里也可以写多个实现,回车分隔)
META-INF/services/com.github.kongwu.spisamples.SuperLoggerConfiguration:
com.github.kongwu.spisamples.XMLConfiguration
然后通过 ServiceLoader 获取我们的 SPI 机制配置的实现类:
ServiceLoa