前面已經講過SPI的基本實現原理了,demo也基本實現了,再來講說SPI。java
http://aphysia.cn/archives/jd...sql
背景:SPI是什麼?SPI
,便是Service Provider Interface
,是一種服務提供(接口實現)發現機制,能夠經過ClassPath路徑下的META-INF/Service
文件查找文件,加載裏面定義的類。
通常能夠用來啓用框架拓展和替換組件,好比在最多見的數據庫鏈接JDBC中,java.sql.Driver
,不一樣的數據庫產商能夠對接口作不同的實現,可是JDK怎麼知作別人有哪些實現呢?這就須要SPI
,能夠查找到接口的實現,對其進行操做。
用兩個字解釋:解耦。數據庫
再簡單點說?
就是Java核心包不知道第三方的包會怎麼實現一個接口,定義了一個規則:你要對這個類拓展,那你就把你的實現類配置到一個文件裏面,文件名就是你要拓展的接口,這樣子,我只要用ServiceLoader
加載接口,我就能夠獲取到實現類的實例。app
對於java核心包來講,我不知道你要怎麼實現接口,可是隻要你按我說的作,配置好,我就能保證你只要引入你本身的包,我就能夠運行到你的代碼。
框架
核心代碼以下:ide
ServiceLoader<DBConnectionService> serviceLoader= ServiceLoader.load(DBConnectionService.class);
因此咱們此時假設本身對ServiceLoader
已經十分好奇了,這是什麼?這是怎麼實現的?這麼牛逼?函數
那就看源碼?夜深人靜剛恰好,白天也看不下去。學習
這裏須要注意的是,這個ServiceLoader
是一個泛型類,實現了Iterable
,說明了什麼?說明它的功能有一部分和集合是差很少的,能夠將多個服務的實現類加載在裏面!!!能夠經過遍歷的方式,一一取出來ui
先看看ServiceLoader
的類成員接口,不急着看load()
函數:this
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; ... }
如今來看load()
函數,其實裏面調用的也仍是serviceLoader
自己的構造器,兩個load方法
service
loader
// 當前線程的類加載器做爲默認加載器 public static <S> ServiceLoader<S> load(Class<S> service) { // 獲取類加載器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); // 調用另一個加載器 return ServiceLoader.load(service, cl); } // 兩個參數的加載方法 public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }
咱們仍是來看serviceLoader
的構造器:
private ServiceLoader(Class<S> svc, ClassLoader cl) { // 要加載的接口 service = Objects.requireNonNull(svc, "Service interface cannot be null"); // 加載器,若是爲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); }
查找加載類的迭代器,究竟是什麼?從名字來看,是一個懶加載器,就是延遲加載,從名字來看,大概能猜到,這個就是使用的時候才加載,真的是這樣麼???接着看下去:
上面 👆 咱們說到ServiceLoader
實際上是一個泛型類,實現了Iterator接口,說明它能夠被遍歷,遍歷的元素是什麼呢?就是上面所說的成員變量LinkedHashMap<String,S> providers
,實現的服務都加載在裏面了。
那咱們就看看遍歷的時候怎麼取的?
這就涉及到了Iterable
接口的方法foreach()
了,咱們就來看看。
總所周知,Iterable
接口的方法以下:
Iterator<T> iterator(); default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); }
那我猜遍歷的方法應該被ServiceLoader
實現的時候,已經重寫了,果不其然:
看獲取Iterator
的方法實現,咱們能夠發現一個驚天㊙️密:也就是其實咱們遍歷的時候,優先是使用已知的集合迭代器,這個集合,就是存儲咱們的服務提供者的集合,也就是已經加載的服務類集合。若是這個集合已經遍歷完成的時候,就會調用查找迭代器去查找,無論next()仍是hasNext()方法,都是這樣的。
同時已經加載的服務,是不能夠被移除的,爲了防止這一點,在移除的時候會返回異常。
public Iterator<S> iterator() { return new Iterator<S>() { // 被查找到的已知的服務提供者的迭代器 Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); // 若是已知的迭代器,也就是存儲服務提供者的集合迭代器,若是這個有下一個元素,那麼就會直接返回true,若是沒有那麼就會調用查找迭代器的hasNext() 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(); } }; }
那Iterator
的其餘方法呢?
固然是用了默認實現了,其餘兩個方法都加了default
關鍵字,ServiceLoader
沒有去實現它,能夠不實現,用默認實現就能夠。
因此咱們的重點是什麼?固然是這個lookupIterator
,它但是一個延遲加載器,爲何這麼說,我以爲應該和上面的分析有關,先遍歷已經加載的,而後沒有了,纔會使用這個延遲查找迭代器,從它的名字就能夠很清楚的看出來,這其實就是一個查找的迭代器,別人都是迭代遍歷已經存在的元素,它倒好,懶到必定程度了,用來查找。
廢話少說,直接看看它怎麼實現的,這麼🐂 牛!
在回頭看看前面初始化的時候,構造是這樣子的:
// 初始化查找加載類的迭代器 lookupIterator = new LazyIterator(service, loader);
能夠看到實際上是將須要加載的服務接口以及類加載器傳遞進來了。
代碼精簡,看👇下面的代碼加註解,應該很清晰了。
private class LazyIterator implements Iterator<S> { // 須要加載的服務 Class<S> service; // 類加載器 ClassLoader loader; // 實現類的url(多個) 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 S next() { // 若是控制訪問器是空的 if (acc == null) { // 調用獲取下一個元素 return nextService(); } else { // 特權動做,重寫的其實也是調用nextService() PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; // 經過控制訪問器的特權訪問,跳過檢查 return AccessController.doPrivileged(action, acc); } } // 是否有下一個元素 public boolean hasNext() { if (acc == null) { // 若是訪問控制器是null,那麼久直接調用hasNextService方法 return hasNextService(); } else { // 生成特權動做 PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run() { return hasNextService(); } }; // 使用訪問控制器執行特權動做 return AccessController.doPrivileged(action, acc); } } // 不能夠移除 public void remove() { throw new UnsupportedOperationException(); } // 是否有下一個元素 private boolean hasNextService() { // 若是下一個元素的全類名名字不爲null,那麼確定是有下一個元素 if (nextName != null) { return true; } // 若是這個實現類的url是空的,怎麼辦?加載進去 if (configs == null) { try { // 獲取全名 String fullName = PREFIX + service.getName(); // 若是類加載器是null if (loader == null) // 經過全名,拿到全名的url,這個url其實就是根據名字找到的配置文件的路徑 configs = ClassLoader.getSystemResources(fullName); else // 不然調用loader自身的方法獲取 configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } // 若是實現類的全限定類名是空的,那就確定須要從文件裏面讀出來呀,由於文件名是接口,裏面配置的是接口的實現類,pending保存的就是實現類 while ((pending == null) || !pending.hasNext()) { // 若是須要讀取的配置沒有要讀的了 if (!configs.hasMoreElements()) { // 直接返回false return false; } // 不然須要解析配置文件裏面的內容 pending = parse(service, configs.nextElement()); } // 下一個service的名字就更新爲讀取到的接口實現類,若是文件裏面什麼都沒有配置,那就頗有多是空的。 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"); } // 判斷服務c是否是實現來自於service,二者是否是繼承關係 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 } }
經過上面的代碼,其實咱們能夠清楚的看到,這個延遲加載器,會去讀取配置類,以及實現的的接口,將實現類全類名放到configs
,而後經過反射的形式構建實例對象,實例化以後才放到providers中,而後返回實現類對象。
值得注意的是,若是訪問控制器是空的,那麼就會調用特權執行:AccessController.doPrivileged(action, acc);
,獲取到服務實現的時候,也會判斷是否是實現來自於咱們須要實現的接口,不然會報錯,調用的是service.isAssignableFrom(c)
。
上面還有一段解析配置的代碼沒有說明,補上:
private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError { // 輸入流 InputStream in = null; // buffer讀入 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 int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError { String ln = r.readLine(); if (ln == null) { return -1; } // 對於#號後面的內容不加載,直接忽略掉 int ci = ln.indexOf('#'); if (ci >= 0) ln = ln.substring(0, ci); ln = ln.trim(); int n = ln.length(); if (n != 0) { if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) fail(service, u, lc, "Illegal configuration-file syntax"); int cp = ln.codePointAt(0); if (!Character.isJavaIdentifierStart(cp)) fail(service, u, lc, "Illegal provider-class name: " + ln); for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { cp = ln.codePointAt(i); if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) fail(service, u, lc, "Illegal provider-class name: " + ln); } // 沒有加載過纔會添加進去 if (!providers.containsKey(ln) && !names.contains(ln)) names.add(ln); } return lc + 1; }
到這裏,serviceLoader
的內容就解讀完畢了,思想挺好的,有兩個迭代器,一個是提供服務的集合自己的迭代器,迭代完成以後,纔會去使用延遲調用lookup
迭代器,觸發尋找操做,若是查找了,那麼就加載到集合中,下次就不用再找了。
查找的時候,直接根據該路徑下的文件,文件名就是接口,接口裏面每一行都是接口的實現類。
實例化接口實現類的時候,實際上是使用了反射,而後判斷類型以後,類型轉換成爲⤴️上轉型,放到已經發現的服務集合中,返回。
【做者簡介】:
秦懷,公衆號【秦懷雜貨店】做者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。這個世界但願一切都很快,更快,可是我但願本身能走好每一步,寫好每一篇文章,期待和大家一塊兒交流。
此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者覈實刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~