javaSPI机制
service provider interface
1 2 3 4 5
| Iterator<ABC> iterator = ServiceLoader.load(ABC.class).iterator(); while (iterator.hasNext()) { ABC next = iterator.next(); }
|
原理
java规定spi的配置文件都在这个目录META-INF/services/
该目录下可以有多个文件,文件的名称必须以class全类名命名。通过ServiceLoader类,读取名字为spi类的全名称的文件
内容为多行,一行为一个class的全类名。该class为spi的实现类。
ServiceLoader入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| public final class ServiceLoader<S> implements Iterable<S> {
private static final String PREFIX = "META-INF/services/";
private final Class<S> service;
private final ClassLoader loader; private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
private LazyIterator lookupIterator; private ServiceLoader(Class<S> svc, ClassLoader cl) { loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; lookupIterator = new LazyIterator(service, loader); } ... public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }
public Iterator<S> iterator() { return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
public boolean hasNext() { if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); }
public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } ... }; } }
|
LazyIterator 真正的加载类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| 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; } public boolean hasNext() { ... configs = configs != null ? configs : loader.getResources("META-INF/services/" + service.getName()); while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; }
pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; ... }
public S next() { ... 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); } ... } ... }
|
总结
本文以jdk1.8来分析,其他版本略有不同。总的来说就是获取资源META-INF/services/
目录下的文件名与spi的class名称一致的文件。
读取里面的实现类,然后通过反射按需实例化并缓存。
但是缓存有局限性哦,只能针对同一个ServiceLoader对象多次产生的迭代器有效哦。