理解Java中的SPI机制
SPI,是JDK内置的一种服务发现机制。
可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。
比如说在jdbc中,由Java程序提供java.sql.Driver这个接口,然后由不同地数据库厂商提供实现类,这是为了约束不同的厂商所写的数据库连接工具。
image.png
SPI机制就是在META-INF下的services文件夹下放一个以需要被实现的接口命名的txt文件,在文件里写上需要实现类的类名,这样SPI机制就可以发现找到实现类。

自己实现一个简单的SPI机制
首先准备一个接口作为标准服务接口提供给不同的厂商。

这里我在testSPi包下建立了一个Hell接口,里面有一个抽象方法sayHello
作为生产商提供两个接口的实现类

两个实现类中分别对sayHello方法进行了重写:


当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类(就是我们刚刚编写的实现类)。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。
package com.hello.dream;
import testSpi.Hell;
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
ServiceLoader<Hell> serviceLoader = ServiceLoader.load(Hell.class);
for (Hell hell : serviceLoader) {
hell.sayHello();
}
}
}
ServiceLoader实现了Iterale,所以可以通过循环来获取所有实现Hell接口的类
运行结果:Hello Spi
Hello World
为什么要放在META-INF下的services中呢?
翻看源码可以找到这一段
private final class LazyClassPathLookupIterator<T>
implements Iterator<Provider<T>>
{
static final String PREFIX = "META-INF/services/";
Set<String> providerNames = new HashSet<>(); // to avoid duplicates
Enumeration<URL> configs;
Iterator<String> pending;
Provider<T> nextProvider;
ServiceConfigurationError nextError;
LazyClassPathLookupIterator() { }
/**
* Parse a single line from the given configuration file, adding the
* name on the line to set of names if not already seen.
*/
private int parseLine(URL u, BufferedReader r, int lc, Set<String> names)
throws IOException
{
String ln = r.readLine();
if (ln == null) {
return -1;
}
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
int start = Character.charCount(cp);
for (int i = start; i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
if (providerNames.add(ln)) {
names.add(ln);
}
}
return lc + 1;
}
定义了一个静态变量PREFIX,制定了从当前位置查找。
找到指定文件的类名后再通过反射中Class.forName()方法加载类对象。
浙公网安备 33010602011771号