緊接着上一篇《經過源碼淺析JDK中的資源加載》,ServiceLoader是SPI(Service Provider Interface)中的服務類加載的核心類,也就是,這篇文章先介紹ServiceLoader的使用方式,再分析它的源碼。java
這裏先列舉一個經典的例子,MySQL的Java驅動就是經過ServiceLoader加載的,先引入mysql-connector-java
的依賴:mysql
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
查看這個依賴的源碼包下的META-INF目錄,可見:sql
咱們接着查看java.lang.DriverManager,靜態代碼塊裏面有:緩存
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }
其中,能夠查看loadInitialDrivers()
有以下的代碼片斷:app
java.lang.DriverManager是啓動類加載器加載的基礎類,可是它能夠加載rt.jar
包以外的類,上篇文章提到,這裏打破了雙親委派模型,緣由是:ServiceLoader中使用了線程上下文類加載器去加載類。這裏JDBC加載的過程就是典型的SPI的使用,總結規律以下:ide
舉個簡單的實例,先定義一個接口和兩個實現:函數
public interface Say { void say(); } public class SayBye implements Say { @Override public void say() { System.out.println("Bye!"); } } public class SayHello implements Say { @Override public void say() { System.out.println("Hello!"); } }
接着在項目的META-INF/services中添加文件以下:源碼分析
最後經過main函數驗證:ui
基於SPI或者說ServiceLoader加載接口實現這種方式也能夠普遍使用在相對基礎的組件中,由於這是一個成熟的規範。this
上面經過一個經典例子和一個實例介紹了ServiceLoader的使用方式,接着咱們深刻分析ServiceLoader的源碼。咱們先看ServiceLoader的類簽名和屬性定義:
public final class ServiceLoader<S> implements Iterable<S>{ //須要加載的資源的路徑的目錄,固定是ClassPath下的META-INF/services/ private static final String PREFIX = "META-INF/services/"; // ServiceLoader須要正在須要加載的類或者接口 // The class or interface representing the service being loaded private final Class<S> service; // ServiceLoader進行類加載的時候使用的類加載器引用 // 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; //基於實例的順序緩存類的實現實例,其中Key爲實現類的全限定類名 // Cached providers, in instantiation order private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 當前的"懶查找"迭代器,這個是ServiceLoader的核心 // The current lazy-lookup iterator private LazyIterator lookupIterator; //暫時忽略其餘代碼... }
ServiceLoader實現了Iterable接口,這一點提示了等下咱們在分析它源碼的時候,須要重點分析iterator()
方法的實現。ServiceLoader依賴於類加載器實例進行類加載,它的核心屬性LazyIterator是就是用來實現iterator()
方法的,下文再重點分析。接着,咱們分析ServiceLoader的構造函數:
public void reload() { //清空緩存 providers.clear(); //構造LazyIterator實例 lookupIterator = new LazyIterator(service, loader); } 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(); }
ServiceLoader只有一個私有的構造函數,也就是它不能經過構造函數實例化,可是要實例化ServiceLoader必須依賴於它的靜態方法調用私有構造去完成實例化操做,而實例化過程主要作了幾步:
reload()
,清空目標加載類的實現類實例的緩存而且構造LazyIterator實例。注意一點是實例方法reload()
的修飾符是public,也就是能夠主動調用去清空目標加載類的實現類實例的緩存和從新構造LazyIterator實例。接着看ServiceLoader提供的靜態方法:
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){ return new ServiceLoader<>(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> loadInstalled(Class<S> service) { ClassLoader cl = ClassLoader.getSystemClassLoader(); ClassLoader prev = null; while (cl != null) { prev = cl; cl = cl.getParent(); } return ServiceLoader.load(service, prev); }
上面的三個公共靜態方法都是用於構造ServiceLoader實例,其中load(Class<S> service, ClassLoader loader)
就是典型的靜態工廠方法,直接調用ServiceLoader的私有構造器進行實例化,除了須要指定加載類的目標類型,還須要傳入類加載器的實例。load(Class<S> service)
實際上也是委託到load(Class<S> service, ClassLoader loader)
,不過它使用的類加載器指定爲線程上下文類加載器,通常狀況下,線程上下文類加載器獲取到的就是應用類加載器(系統類加載器)。loadInstalled(Class<S> service)
方法又看出了"雙親委派模型"的影子,它指定類加載器爲最頂層的啓動類加載器,最後也是委託到load(Class<S> service, ClassLoader loader)
。接着咱們須要重點分析ServiceLoader#iterator()
:
public Iterator<S> iterator() { //Iterator的匿名實現 return new Iterator<S>() { //目標類實現類實例緩存的Map的Entry的迭代器實例 Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); //先從緩存中判斷是否有下一個實例,不然經過懶加載迭代器LazyIterator去判斷是否存在下一個實例 public boolean hasNext() { if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); } //若是緩存中判斷是否有下一個實例,若是有則從緩存中的值直接返回 //不然經過懶加載迭代器LazyIterator獲取下一個實例 public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } //不支持移除操做,直接拋異常 public void remove() { throw new UnsupportedOperationException(); } }; }
iterator()
內部僅僅是Iterator接口的匿名實現,hasNext()
和next()
方法都是優先判斷緩存中是否已經存在實現類的實例,若是存在則直接從緩存中返回,不然調用懶加載迭代器LazyIterator的實例去獲取,而LazyIterator自己也是一個Iterator接口的實現,它是ServiceLoader的一個私有內部類,源碼以下:
private class LazyIteratorimplements 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; } private boolean hasNextService() { //若是下一個須要加載的實現類的全限定類名不爲null,則說明資源中存在內容 if (nextName != null) { return true; } //若是加載的資源的URL集合爲null則嘗試進行加載 if (configs == null) { try { //資源的名稱,META-INF/services + '須要加載的類的全限定類名' //這樣獲得的恰好是須要加載的文件的資源名稱 String fullName = PREFIX + service.getName(); //這裏其實ClassLoader實例應該不會爲null if (loader == null) configs = ClassLoader.getSystemResources(fullName); else //從ClassPath加載資源 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 { //反射構造Class<S>實例 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 { //經過Class#newInstance()進行實例化,而且強制轉化爲對應的類型的實例 S p = service.cast(c.newInstance()); //添加緩存,Key爲實現類的全限定類名,Value爲實現類的實例 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(); } }
LazyIterator
也是Iterator接口的實現,它的Lazy特性代表它老是在ServiceLoader的Iterator接口匿名實現iterator()
執行hasNext()
判斷是否有下一個實現或者next()
獲取下一個實現類的實例的時候纔會"懶判斷"或者"懶加載"下一個實現類的實例。最後是加載資源文件後對資源文件的解析過程的源碼:
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); } } //返回的是ArrayList的迭代器實例 return names.iterator(); } //解析資源文件中每一行的內容 private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names)throws IOException, ServiceConfigurationError{ // 下一行沒有內容,返回-1,便於上層能夠跳出循環 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) { //不能存在空格字符' '和特殊字符'\t' if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) fail(service, u, lc, "Illegal configuration-file syntax"); int cp = ln.codePointAt(0); //判斷第一個char是否一個合法的Java起始標識符 if (!Character.isJavaIdentifierStart(cp)) fail(service, u, lc, "Illegal provider-class name: " + ln); //判斷全部其餘字符串是否屬於合法的Java標識符 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; }
整個資源文件的解析過程並不複雜,主要包括文件內容的字符合法性判斷和緩存避免重複加載的判斷。
SPI被普遍使用在第三方插件式類庫的加載,最多見的如JDBC、JNDI、JCE(Java加密模塊擴展)等類庫。理解ServiceLoader的工做原理有助於編寫擴展性良好的可插拔的類庫。
(本文完 c-1-d e-20181014)