SPI即Service Provider Interface, 即服务提供者接口. 是由JDK内置提供的一种服务接口发现机制,主要是方便开发人员进行接口扩展或者组件替换,一般多用于框架开发中.
1.SPI的原理
SPI接口发现机制功能的实现主要是通过java.util.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
| 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 final AccessControlContext acc;
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
private LazyIterator lookupIterator; }
|
ServiceLoader 会去/META-INF/service/
目录底下读取所有的配置文件,将文件URL存放到一个枚举集合Enumeration<URL>
中,迭代这个集合,用文件流的方式读取每一个URL代表的文件,在每个文件中逐行读取,取得要装载类的全限定名,如果类名是合规的,再用
1
| Class.forName(className, classLoader)
|
的方式获得要装载的类的字节码对象,再调用反射方法 newInstance()
来进行类的实例化,最后将其放入providers缓存中.
2. 装载类的具体过程
参照着源码,梳理一下具体过程
- 应用程序调用
ServiceLoader.load()
方法
在ServiceLoader.load()
方法内先创建一个新的ServiceLoader,并初始化该类中的成员变量.
1 2 3 4 5 6
| 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(); }
|
- 接着调用
reload()
方法,reload()
方法主要做了两件事,一是清空原有的providers缓存,二是实例化一个查找迭代器 LazyIterator.
1 2 3 4
| public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); }
|
- LazyIterator类事实上是内部
Iterator<S>
接口的一个私有化自我实现. 当调用方调用到Iterator
的hasNext()
方法时,实际上会调用到LazyIterator<S>
的hasNextService()
方法.
1 2 3 4 5 6 7 8 9 10 11 12
| 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); } }
|
- 在
hasNextService()
方法里边,就会取读取/META-INF/service
目录底下的配置文件,并将文件以URL的形式存放在一个枚举集合中. 再通过 parse()
方法读取每一个URL文件中的类名(逐行读取),并将类名存放在Iterator 中返回.
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
| 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()); } }
private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError { InputStream in = null; BufferedReader r = null; ArrayList<String> names = new ArrayList<>(); try { in = u.openStream(); r = new BufferedReader(new InputStreamReader(in, "utf-8")); int lc = 1; while ((lc = parseLine(service, u, r, lc, names)) >= 0); } catch (IOException x) { fail(service, "Error reading configuration file", x); } finally { try { if (r != null) r.close(); if (in != null) in.close(); } catch (IOException y) { fail(service, "Error closing configuration file", y); } } return names.iterator(); }
|
- 当调用方调用到
next()
方法时,实际上调用的是LazyIterator 的 nextService()
方法, 在这个方法中,会迭代Iterator集合,依次取出类名通过反射来进行类的装载和实例化. 对应的源码如下:
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
| 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(); }
|