1.什么是SPI?
SPI:Service Provider Interface,是JDK提供的为某个接口寻找服务实现的机制。为了实现不对实现类进行硬编码,在程序里动态指明。
2.使用场景?
2.1 common-logging
apache最早提供的日志的门面接口。只有接口,没有实现。具体方案由各提供商实现, 发现日志提供商是通过扫描 META-INF/services/org.apache.commons.logging.LogFactory配置文件,通过读取该文件的内容找到日志提工商实现类。只要我们的日志实现里包含了这个文件,并在文件里制定 LogFactory工厂接口的实现类即可。
2.2 JDBC
META-INF.services包下有个java.sql.Driver文件:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
其中com.mysql.jdbc.Driver类是实现了java.sql.Driver接口,下面那com.mysql.fabric.jdbc.FabricMySQLDriver才是真正的实现类
3.例子:
3.1接口:
package com.test.spi;
public interface HelloInterface {
public void sayHello();
}
3.2实现类:
public class ImageHello implements HelloInterface {
@Override
public void sayHello() {
System.out.println("Image Hello");
}
}
public class TextHello implements HelloInterface {
@Override
public void sayHello() {
System.out.println("Text Hello.");
}
}
3.3 文件
在src路径下的META-INF/services/com.test.spi.HelloInterface
com.test.spi.impl.TextHello
com.test.spi.impl.ImageHello
3.4 Main方法
public class SPIMain {
public static void main(String[] args) {
ServiceLoader<HelloInterface> loaders = ServiceLoader.load(HelloInterface.class);
for (HelloInterface in : loaders) {
in.sayHello();
}
}
}
3.5 运行结果:
Text Hello.
Image Hello
4.原理分析
4.1ServiceLoader实现了Iterable这个接口,因此可以在遍历的时候,重新进行lazy加载。
public final class ServiceLoader<S>
implements Iterable<S>
{
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
}
4.2
private class LazyIterator
implements Iterator<S>
{
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//延迟加载
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
}
参考: