SPI 机制(Service Provider Interface)其实源自服务提供者框架(Service Provider Framework,参考【EffectiveJava】page6),是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了 spi 接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔
在开发中,有很多地方都适用到了 java 的 spi 机制;例如:JSBC设计
…等,这里我们不去研究这些示例,直接看一下他的实现方式
其实 java
的 spi
核心核心就是 ServiceLoader
这个类;我们可以通过在 resource/META-INF/services
下,放置要实例化的类实现的统一接口全路径的文件,在里面每行写上实例化类的全路径,即可使用 spi
的方式实例化,如下:
首先定义一个接口:
package com.spi.test.source;
public interface Developer {
void hello();
}
然后实现两个实现类:
package com.spi.test.source;
public class Make1Developer implements Developer {
public void hello() {
System.out.println("make1 的 develop");
}
}
public class Make2Developer implements Developer {
public void hello() {
System.out.println("make2 的 develop");
}
}
然后在 resource/META-INF/services
下创建 com.spi.test.source.Developer
文件内容如下:
com.spi.test.source.Make1Developer
com.spi.test.source.Make2Developer
测试类如下:
public static void main(String[] args) {
ServiceLoader<Developer> serviceLoader = ServiceLoader.load(Developer.class);
// 循环调用下实例的方法
serviceLoader.forEach(item -> {
System.out.println("正在执行的类是:" + item.getClass().toString());
item.hello();
});
}
上线是 jdk
自己携带的 SPI
功能,但是他设计的并不太好,只实现了基础的功能;另外 dubbo
对 SPI
也有自己的扩展方式,而且实现上也有了优化,
dubbo spi
对比 java spi
的优化点:
有兴趣可以去看一下 dubbo-common.jar
下的 org.apache.dubbo.common.extension.ExtensionLoader
类的实现;这里不在详细说明,我们这里只简单模仿 java spi
自己扩展一个 spi
的功能
首先定义一个注解,这里我们加一步校验,就是接口上必须加上注解,才可以将实现其接口的类实例化:
package com.spi.test.my.source;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SpiActive {
}
然后自己实现一个扩展类加载器辅助类:
package com.spi.test.my.source;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;
import static java.nio.charset.StandardCharsets.UTF_8;
public class ExpansionLoader<T> implements Iterable<T> {
/**
* 约定第三方实现配置文件目录
**/
private static final String SERVICE_DIRECTORY = "META-INF/expansions/";
/**
* 接口的类型,用于获取此接口下的第三方实现
**/
private final Class<T> type;
/**
* 实例列表,用于保存获取到的第三方实现的列表
**/
private Set<T> targets;
public ExpansionLoader(Class<T> type) {
this.type = type;
this.targets = this.loadExtensionFile();
}
/**
* 工厂方法,用于通过接口获取第三方实现的类加载器
*
* @param type 接口的类型
* @return 返回一个指定接口类型的类加载器辅助类
**/
public static <T> ExpansionLoader<T> load(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Spi需要知道你想要找到哪个功能的第三方实现!");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("只支持寻找接口类型的第三方实现!");
}
if (type.getAnnotation(SpiActive.class) == null) {
throw new IllegalArgumentException("目标接口必须被@Spi注解标注!");
}
return new ExpansionLoader<>(type);
}
private Set<T> loadExtensionFile() {
String fileName = ExpansionLoader.SERVICE_DIRECTORY + this.type.getName();
try {
ClassLoader classLoader = ExpansionLoader.class.getClassLoader();
Enumeration<URL> urls = classLoader.getResources(fileName);
if (urls == null) {
return Collections.emptySet();
}
URL resourceUrl = urls.nextElement();
return loadResource(classLoader, resourceUrl);
} catch (IOException e) {
return Collections.emptySet();
}
}
private Set<T> loadResource(ClassLoader classLoader, URL resourceUrl) {
Set<T> set = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci == 0) {
continue;
} else if (ci > 0) {
line = line.substring(0, ci);
}
Class<T> clazz = (Class<T>) classLoader.loadClass(line.trim());
set.add(clazz.newInstance());
}
} catch (Exception e) {}
return set;
}
@Override
public Iterator<T> iterator() {
return this.targets.iterator();
}
}
以上是工具部分;下面开始定义第三方需要实现的接口:
package com.spi.test.my;
import com.spi.test.my.source.SpiActive;
@SpiActive
public interface Production {
void hello();
}
然后添加两个实现类:
package com.spi.test.my;
public class Make1Production implements Production {
@Override
public void hello() {
System.out.println("make1 的 Production");
}
}
public class Make2Production implements Production {
@Override
public void hello() {
System.out.println("make2 的 Production");
}
}
然后在 resource/META-INF/expansions
下创建 com.spi.test.my.Production
文件内容如下:
com.spi.test.my.Make1Production
com.spi.test.my.Make2Production
测试类如下:
public static void main(String[] args) {
ExpansionLoader<Production> extensionLoader = ExpansionLoader.load(Production.class);
extensionLoader.forEach(item -> {
System.out.println("正在执行的类是:" + item.getClass().toString());
item.hello();
});
}