spi 02-spi 的實戰解決 slf4j 包衝突問題java
spi 05-dubbo adaptive extension 自適應拓展github
ServiceLoader.load方法內先建立一個新的ServiceLoader,並實例化該類中的成員變量,包括:緩存
loader(ClassLoader類型,類加載器) acc(AccessControlContext類型,訪問控制器) providers(LinkedHashMap<String,S>類型,用於緩存加載成功的類) lookupIterator(實現迭代器功能)
ServiceLoader 先判斷成員變量 providers 對象中( LinkedHashMap<String,S>
類型)是否有緩存實例對象,若是有緩存,直接返回。安全
若是沒有緩存,執行類的裝載,實現以下:多線程
(1) 讀取META-INF/services/下的配置文件,得到全部能被實例化的類的名稱,值得注意的是,ServiceLoader能夠跨越jar包獲取META-INF下的配置文件,具體加載配置的實現代碼以下:
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); }
(2) 經過反射方法 Class.forName() 加載類對象,並用 instance() 方法將類實例化。
(3) 把實例化後的類緩存到 providers 對象中,( LinkedHashMap<String,S>
類型,而後返回實例對象。
看到這裏,實際上對咱們理解 SPI 的標準頗有幫助,好比爲何須要無參構造器。
下面咱們簡單的總體過一遍源碼。
在 java.util
包下。
public final class ServiceLoader<S> implements Iterable<S>
從註釋中可知該類是 jdk1.6 開始支持,繼承自 Iterable。
PREFIX 對應的就是咱們指定 SPI 文件配置的地方。
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;
咱們回憶一下使用時的方式:
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ServiceLoader<Say> loader = ServiceLoader.load(Say.class, classLoader); for (Say say : loader) { say.say(); }
這裏也就展現了方法的入口,獲取當前的 ClassLoader,咱們來看一下 load 方法。
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }
直接建立了一個實例,這個構造器方法是 private 的:
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(); }
爲了提供遍歷,提供了默認的 ClassLoader 實現
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
這裏作了從新加載,清空了 providers,而且新建了一個 LazyIterator。
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
其實就是一個 cache,每次初始化會作清空。
public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); }
咱們來看一下這個迭代器的實現
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(); } }
其中最核心的兩個方法 hasNextService()
會去指定的目錄下處理配置信息,hasNextService()
有以下核心實現。
c = Class.forName(cn, false, loader); // 建立而且緩存 S p = service.cast(c.newInstance()); providers.put(cn, p);
hasNextService() 方法中的 parse 也值得看一下。
實際就是去解析文件夾下的配置文件,按照行讀取。
能夠看出來,默認使用的是 utf-8
文件編碼。
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(); }
這個方法出現了屢次,實際上只是一個報錯信息,知道便可:
private static void fail(Class<?> service, String msg) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ": " + msg); }
這裏是遍歷了 loader,實際上就是 java 的一個語法糖。
對應的是 Iterator, 對應的 hasNext() 和 next() 方法。
for (Say say : loader) { say.say(); }
這裏就是遍歷了 cache 中的實現,至於 cache 中的信息怎麼來的,就是 LazyIterator 遍歷過程當中的建立實例+cache。
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(); } public void remove() { throw new UnsupportedOperationException(); } }; }
但就源碼而言,實現機制並不複雜。
可是思想比較不錯,也帶了很大的方便。
當咱們看了源碼以後,對於優缺點實際會有更加清晰的認識。
使用Java SPI機制的優點是實現解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離,而不是耦合在一塊兒。
應用程序能夠根據實際業務狀況啓用框架擴展或替換框架組件。
相比使用提供接口jar包,供第三方服務模塊實現接口的方式,SPI的方式使得源框架,沒必要關心接口的實現類的路徑,能夠不用經過下面的方式獲取接口實現類:
代碼硬編碼import 導入實現類
指定類全路徑反射獲取:例如在JDBC4.0以前,JDBC中獲取數據庫驅動類須要經過 Class.forName("com.mysql.jdbc.Driver")
,相似語句先動態加載數據庫相關的驅動,而後再進行獲取鏈接等的操做
經過SPI的方式,第三方服務模塊實現接口後,在第三方的項目代碼的 META-INF/services 目錄下的配置文件指定實現類的全路徑名,源碼框架便可找到實現類
雖然ServiceLoader也算是使用的延遲加載,可是基本只能經過遍歷所有獲取,也就是接口的實現類所有加載並實例化一遍。若是你並不想用某些實現類,它也被加載並實例化了,這就形成了浪費。
獲取某個實現類的方式不夠靈活,只能經過Iterator形式獲取,不能根據某個參數來獲取對應的實現類。
實際上 hibernate-validator/dubbo 等常見框架,都有用到 SPI。
後續咱們將一塊兒來看一下 dubbo 中的實現,看看 dubbo 是如何解決這些不足之處的。