源碼級深度理解 Java SPI( 二 )

3.2 ServiceLoader 的工作流程(1)ServiceLoader.load 靜態方法
應用程序加載 Java SPI 服務,都是先調用 ServiceLoader.load 靜態方法 。
ServiceLoader.load 靜態方法的作用是:
① 指定類加載 ClassLoader 和訪問控制上下文;
【源碼級深度理解 Java SPI】② 然后 , 重新加載 SPI 服務

  • 清空緩存中所有已實例化的 SPI 服務
  • 根據 ClassLoader 和 SPI 類型,創建懶加載迭代器
這里 , 摘錄 ServiceLoader.load 相關源碼,如下:
// service 傳入的是期望加載的 SPI 接口類型// loader 是用于加載 SPI 服務的類加載器public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {return new ServiceLoader<>(service, loader);}public void reload() {// 清空緩存中所有已實例化的 SPI 服務providers.clear();// 根據 ClassLoader 和 SPI 類型,創建懶加載迭代器lookupIterator = new LazyIterator(service, loader);}// 私有構造方法// 重新加載 SPI 服務private ServiceLoader(Class<S> svc, ClassLoader cl) {service = Objects.requireNonNull(svc, "Service interface cannot be null");// 指定類加載 ClassLoader 和訪問控制上下文loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;// 然后,重新加載 SPI 服務reload();}(2)應用程序通過 ServiceLoader 的 iterator 方法遍歷 SPI 實例
ServiceLoader 的類定義,明確了 ServiceLoader 類實現了 Iterable<T> 接口 , 所以 , 它是可以迭代遍歷的 。實際上,ServiceLoader 類維護了一個緩存 providers( LinkedHashMap 對象),緩存 providers 中保存了已經被成功加載的 SPI 實例,這個 Map 的 key 是 SPI 接口實現類的全限定名 , value 是該實現類的一個實例對象 。
當應用程序調用 ServiceLoader 的 iterator 方法時,ServiceLoader 會先判斷緩存 providers 中是否有數據:如果有,則直接返回緩存 providers 的迭代器;如果沒有,則返回懶加載迭代器的迭代器 。
public Iterator<S> iterator() {return new Iterator<S>() {// 緩存 SPI providersIterator<Map.Entry<String,S>> knownProviders= providers.entrySet().iterator();// lookupIterator 是 LazyIterator 實例,用于懶加載 SPI 實例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();}};}(3)懶加載迭代器的工作流程
上面的源碼中提到了,lookupIterator 是 LazyIterator 實例,而 LazyIterator 用于懶加載 SPI 實例 。那么,LazyIterator 是如何工作的呢?
這里,摘取 LazyIterator 關鍵代碼
hasNextService 方法:
  • 拼接 META-INF/services/ + SPI 接口全限定名
  • 通過類加載器 , 嘗試加載資源文件
  • 解析資源文件中的內容,獲取 SPI 接口的實現類的全限定名 nextName
nextService 方法:
  • hasNextService() 方法解析出了 SPI 實現類的的全限定名 nextName,通過反射,獲取 SPI 實現類的類定義 Class 。
  • 然后,嘗試通過 Class 的 newInstance 方法實例化一個 SPI 服務對象 。如果成功 , 則將這個對象加入到緩存 providers 中并返回該對象 。
private boolean hasNextService() {if (nextName != null) {return true;}if (configs == null) {try {// 1.拼接 META-INF/services/ + SPI 接口全限定名// 2.通過類加載器,嘗試加載資源文件// 3.解析資源文件中的內容String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = 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 s");}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}3.3 SPI 和類加載器通過上面兩個章節中,走讀 ServiceLoader 代碼,我們已經大致了解 Java SPI 的工作原理,即通過 ClassLoader 加載 SPI 配置文件,解析 SPI 服務 , 然后通過反射,實例化 SPI 服務實例 。我們不妨思考一下 , 為什么加載 SPI 服務時 , 需要指定類加載器 ClassLoader 呢?

推薦閱讀