我们常见的JDBC、日志框架slf4j、JavaMail、Spring等组件都基于
SPI代码实战
之所以说区别于Java的一些设计模式,因为Java有一些实现能实现
package com.fishmaple.diantao; public interfaceSPITest { //定义一个获取version的方法 Integer getVersion(); }
然后我们定义两个不同的实现:
package com.fishmaple.diantao; public classSPITestImpl implements SPITest { public Integer getVersion() { System.out.println("version1"); return 1; } }
package com.fishmaple.diantao; public classSPITestImpl2 implements SPITest { public Integer getVersion() { System.out.println("version2"); return 2; } }
截止到目前,上面还没有使用到Java

文件内容只要写
com.fishmaple.diantao.SPITestImpl2
随后在启动服务后,我们使用
public class DiantaoApplication {
public static void main(String[] args) {
ServiceLoader< SPITest> load = ServiceLoader.load( SPITest.class);
for( SPITest spiTest:load){
spiTest.getVersion();
}
}
}
SPI原理浅析
细看代码,不难发现,
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;
//缓存已经被加载过的 SPI // Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
public static <S> ServiceLoader<S> load(Class<S> service) {
//1 获取 类加载器 ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
//2 这里创建了一个新实例
return new ServiceLoader<>(service, loader);
}
//3 不难看出,这里的初始化和之后的reload其实并没有创建出 SPI需要的实例,(因为是lazyload)
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
// 4 迭代器,捡重点看
private class LazyIterator
implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration< URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
//懒加载对应的类
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
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
}
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
如此便可以实现在首次轮询时加载对应的
案例 - JDBC


