我们常见的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(); } }
如此便可以实现在首次轮询时加载对应的