一、spi机制
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。
SPI整体机制图如下:
当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。
SPI扩展机制应用场景有很多,比如Common-Logging,JDBC,Dubbo等等。
SPI流程:
- 有关组织和公式定义接口标准
- 第三方提供具体实现: 实现具体方法, 配置 META-INF/services/${interface_name} 文件
- 开发者使用
比如JDBC场景下:
- 首先在Java中定义了接口java.sql.Driver,并没有具体的实现,具体的实现都是由不同厂商提供。
- 在MySQL的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。
- 同样在PostgreSQL的jar包PostgreSQL-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是PostgreSQL对Java的java.sql.Driver的实现。
二、手写简易版本的spi
jdk的spi固然也挺好用但是有的时候不能满足我们的特定需求,我们也可手动实现一个,代码如下:
public class SpiHandler {
// 定义一个basePath
private static final String BASE_PATH = "META-INF/yrpc-services";
// 先定义一个缓存,保存spi相关的原始内容
private static final Map<String, List<String>> SPI_CONTENT = new ConcurrentHashMap<>(8);
// 缓存的是每一个接口所对应的实现的实例
private static final Map<Class<?>,List<ObjectWrapper<?>>> SPI_IMPLEMENT = new ConcurrentHashMap<>(32);
// 加载当前类之后需要将spi信息进行保存,避免运行时频繁执行IO
static {
// todo 怎么加载当前工程和jar包中的classPath中的资源
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL fileUrl = classLoader.getResource(BASE_PATH);
if(fileUrl != null) {
File file = new File(fileUrl.getPath());
File[] children = file.listFiles();
if(children != null) {
for (File child : children) {
String key = child.getName();
List<String> value = getImplNames(child);
SPI_CONTENT.put(key, value);
}
}
}
}
/**
* 获取第一个和当前服务相关的实例
* @param clazz 一个服务接口的class实例
* @return 实现类的实例
* @param <T>
*/
public synchronized static <T> ObjectWrapper<T> get(Class<T> clazz) {
// 1、优先走缓存
List<ObjectWrapper<?>> objectWrappers = SPI_IMPLEMENT.get(clazz);
if(objectWrappers != null && objectWrappers.size() > 0){
return (ObjectWrapper<T>)objectWrappers.get(0);
}
// 2、构建缓存
buildCache(clazz);
List<ObjectWrapper<?>> result = SPI_IMPLEMENT.get(clazz);
if (result == null || result.size() == 0){
return null;
}
// 3、再次尝试获取第一个
return (ObjectWrapper<T>)result.get(0);
}
/**
* 获取所有和当前服务相关的实例
* @param clazz 一个服务接口的class实例
* @return 实现类的实例集合
* @param <T>
*/
public synchronized static <T> List<ObjectWrapper<T>> getList(Class<T> clazz) {
// 1、优先走缓存
List<ObjectWrapper<?>> objectWrappers = SPI_IMPLEMENT.get(clazz);
if(objectWrappers != null && objectWrappers.size() > 0){
return objectWrappers.stream().map( wrapper -> (ObjectWrapper<T>)wrapper )
.collect(Collectors.toList());
}
// 2、构建缓存
buildCache(clazz);
// 3、再次获取
objectWrappers = SPI_IMPLEMENT.get(clazz);
if(objectWrappers != null && objectWrappers.size() > 0){
return objectWrappers.stream().map( wrapper -> (ObjectWrapper<T>)wrapper )
.collect(Collectors.toList());
}
return new ArrayList<>();
}
/**
* 获取文件所有的实现名称
* @param child 文件对象
* @return 实现类的权限定名称结合
*/
private static List<String> getImplNames(File child) {
try(
FileReader fileReader = new FileReader(child);
BufferedReader bufferedReader = new BufferedReader(fileReader)
)
{
List<String> implNames = new ArrayList<>();
while (true){
String line = bufferedReader.readLine();
if (line == null || "".equals(line)) break;
implNames.add(line);
}
return implNames;
} catch (IOException e){
log.error("读取spi文件时发生异常.",e);
}
return null;
}
/**
* 构建clazz相关的缓存
* @param clazz 一个类的class实例
*/
private static void buildCache(Class<?> clazz) {
// 1、通过clazz获取与之匹配的实现名称
String name = clazz.getName();
List<String> implNames = SPI_CONTENT.get(name);
if(implNames == null || implNames.size() == 0){
return;
}
// 2、实例化所有的实现
List<ObjectWrapper<?>> impls = new ArrayList<>();
for (String implName : implNames) {
try {
// 首先进行分割
String[] codeAndTypeAndName = implName.split("-");
if(codeAndTypeAndName.length != 3){
throw new SpiException("您配置的spi文件不合法");
}
Byte code = Byte.valueOf(codeAndTypeAndName[0]);
String type = codeAndTypeAndName[1];
String implementName = codeAndTypeAndName[2];
Class<?> aClass = Class.forName(implementName);
Object impl = aClass.getConstructor().newInstance();
ObjectWrapper<?> objectWrapper = new ObjectWrapper<>(code,type,impl);
impls.add(objectWrapper);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e){
log.error("实例化【{}】的实现时发生了异常",implName,e);
}
}
SPI_IMPLEMENT.put(clazz,impls);
}
}
这样我们只需要将目录结构按照如下的方式建立,并按照规范填写响应的内容即可完成spi的自动发现。