【Java】ServiceLoader原始碼分析
ServiceLoader主要的功能是用來完成對SPI的provider的載入。
先看下它的成員:
1 public final class ServiceLoader<S> 2 implements Iterable<S> { 3 4 private static final String PREFIX = "META-INF/services/"; 5 6 // The class or interface representing the service being loaded 7 private final Class<S> service; 8 9 // The class loader used to locate, load, and instantiate providers 10 private final ClassLoader loader; 11 12 // The access control context taken when the ServiceLoader is created 13 private final AccessControlContext acc; 14 15 // Cached providers, in instantiation order 16 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 17 18 // The current lazy-lookup iterator 19 private LazyIterator lookupIterator; 20 21 ...... 22 23 }
可以看到他首先是實現了Iterable介面,可以迭代。
PREFIX:指明瞭路徑是在"META-INF/services/"下。
service:表示正在載入的服務的類或介面。
loader:使用的類載入器。
acc:建立ServiceLoader時獲取的訪問控制上下文。
providers :快取的服務提供集合。
lookupIterator:是其內部使用的迭代器,用於類的懶載入,只有在迭代時載入。
其構造方法是一個private方法,不對外提供,在使用時我們需要呼叫其靜態的load方法,由其自身產生ServiceLoader物件:
1 public static <S> ServiceLoader<S> load(Class<S> service) { 2 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 3 return ServiceLoader.load(service, cl); 4 } 5 6 public static <S> ServiceLoader<S> load(Class<S> service, 7 ClassLoader loader) { 8 return new ServiceLoader<>(service, loader); 9 }
可以看到對load方法進行了過載,其中引數service是要載入的類;單參方法沒有類載入器,使用的是當前執行緒的類載入器;最後呼叫的是雙參的load方法;而雙參的load方法也很簡單,只是直接呼叫ServiceLoader的構造方法,例項化了一個物件。
1 private ServiceLoader(Class<S> svc, ClassLoader cl) { 2 service = Objects.requireNonNull(svc, "Service interface cannot be null"); 3 loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; 4 acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; 5 reload(); 6 }
可以看到其構造方法邏輯依舊很簡單,首先是判斷傳入的svc(即傳入的service)是否為空,若是為空直接報異常,否則給service 成員賦值:
1 public static <T> T requireNonNull(T obj, String message) { 2 if (obj == null) 3 throw new NullPointerException(message); 4 return obj; 5 }
然後給進行cl的非空判斷,給loader 成員賦值;接著給acc 成員賦值,其根據是否設定了安全管理器SecurityManager來賦值;最後呼叫reload方法。
1 public void reload() { 2 providers.clear(); 3 lookupIterator = new LazyIterator(service, loader); 4 }
可以看到reload方法是一個public方法,那麼在每次呼叫reload時就需要將之前載入的清空掉,所以直接使用providers這個map的clear方法清空掉快取;接著使用剛才賦值後的service和loader產生一個LazyIterator物件賦值給lookupIterator成員。
LazyIterator是ServiceLoader的內部類,其定義如下:
1 private class LazyIterator 2 implements Iterator<S> { 3 Class<S> service; 4 ClassLoader loader; 5 Enumeration<URL> configs = null; 6 Iterator<String> pending = null; 7 String nextName = null; 8 9 private LazyIterator(Class<S> service, ClassLoader loader) { 10 this.service = service; 11 this.loader = loader; 12 } 13 ...... 14 }
這裡就可以看到ServiceLoader的實際載入過程就交給了LazyIterator來做,將ServiceLoader的service和loader成員分別賦值給了LazyIterator的service和loader成員。
configs是服務的URL列舉;
pending是儲存要載入的服務的名稱集合;
nextName是下一個要載入的服務名稱;
ServiceLoader實現了Iterable介面,其實現的iterator方法如下:
1 public Iterator<S> iterator() { 2 return new Iterator<S>() { 3 Iterator<Map.Entry<String,S>> knownProviders 4 = providers.entrySet().iterator(); 5 6 public boolean hasNext() { 7 if (knownProviders.hasNext()) 8 return true; 9 return lookupIterator.hasNext(); 10 } 11 12 public S next() { 13 if (knownProviders.hasNext()) 14 return knownProviders.next().getValue(); 15 return lookupIterator.next(); 16 } 17 18 public void remove() { 19 throw new UnsupportedOperationException(); 20 } 21 22 }; 23 }
可以看到它是直接建立了一個Iterator物件返回;其knownProviders成員直接獲取providers的entrySet集合的迭代器;在hasNext和next方法中我們可以看到,它是先通過判斷knownProviders裡有沒有(即providers),若沒有再去lookupIterator中找;
前面我們可以看到providers裡並沒用put任何東西,那麼就說明put操作也是在lookupIterator中完成的。
先看到lookupIterator的next方法:
1 public S next() { 2 if (acc == null) { 3 return nextService(); 4 } else { 5 PrivilegedAction<S> action = new PrivilegedAction<S>() { 6 public S run() { return nextService(); } 7 }; 8 return AccessController.doPrivileged(action, acc); 9 } 10 }
首先根據判斷acc是否為空,若為空則說明沒有設定安全策略直接呼叫nextService方法,否則以特權方式呼叫nextService方法。
1 private S nextService() { 2 if (!hasNextService()) 3 throw new NoSuchElementException(); 4 String cn = nextName; 5 nextName = null; 6 Class<?> c = null; 7 try { 8 c = Class.forName(cn, false, loader); 9 } catch (ClassNotFoundException x) { 10 fail(service, 11 "Provider " + cn + " not found"); 12 } 13 if (!service.isAssignableFrom(c)) { 14 fail(service, 15 "Provider " + cn + " not a subtype"); 16 } 17 try { 18 S p = service.cast(c.newInstance()); 19 providers.put(cn, p); 20 return p; 21 } catch (Throwable x) { 22 fail(service, 23 "Provider " + cn + " could not be instantiated", 24 x); 25 } 26 throw new Error(); // This cannot happen 27 }
首先根據hasNextService方法判斷,若為false直接丟擲NoSuchElementException異常,否則繼續執行。
hasNextService方法:
1 private boolean hasNextService() { 2 if (nextName != null) { 3 return true; 4 } 5 if (configs == null) { 6 try { 7 String fullName = PREFIX + service.getName(); 8 if (loader == null) 9 configs = ClassLoader.getSystemResources(fullName); 10 else 11 configs = loader.getResources(fullName); 12 } catch (IOException x) { 13 fail(service, "Error locating configuration files", x); 14 } 15 } 16 while ((pending == null) || !pending.hasNext()) { 17 if (!configs.hasMoreElements()) { 18 return false; 19 } 20 pending = parse(service, configs.nextElement()); 21 } 22 nextName = pending.next(); 23 return true; 24 }
hasNextService方法首先根據nextName成員是否為空判斷,若不為空,則說明已經初始化過了,直接返回true,否則繼續執行。接著configs成員是否為空,configs 是一個URL的列舉,若是configs 沒有初始化,就需要對configs初始化。
configs初始化邏輯也很簡單,首先根據PREFIX字首加上PREFIX的全名得到完整路徑,再根據loader的有無,獲取URL的列舉。其中fail方法時ServiceLoader的靜態方法,用於異常的處理,後面給出。
在configs初始化完成後,還需要完成pending的初始化或者新增。
可以看到只有當pending為null,或者沒有元素時才進行迴圈。迴圈時若是configs裡沒有元素,則直接返回false;否則呼叫ServiceLoader的parse方法,通過service和URL給pending賦值;
parse方法:
1 private Iterator<String> parse(Class<?> service, URL u) 2 throws ServiceConfigurationError { 3 InputStream in = null; 4 BufferedReader r = null; 5 ArrayList<String> names = new ArrayList<>(); 6 try { 7 in = u.openStream(); 8 r = new BufferedReader(new InputStreamReader(in, "utf-8")); 9 int lc = 1; 10 while ((lc = parseLine(service, u, r, lc, names)) >= 0); 11 } catch (IOException x) { 12 fail(service, "Error reading configuration file", x); 13 } finally { 14 try { 15 if (r != null) r.close(); 16 if (in != null) in.close(); 17 } catch (IOException y) { 18 fail(service, "Error closing configuration file", y); 19 } 20 } 21 return names.iterator(); 22 }
可以看到parse方法直接通過URL開啟輸入流,通過parseLine一行一行地讀取將結果儲存在names數組裡。
parseLine方法:
1 private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, 2 List<String> names) 3 throws IOException, ServiceConfigurationError { 4 String ln = r.readLine(); 5 if (ln == null) { 6 return -1; 7 } 8 int ci = ln.indexOf('#'); 9 if (ci >= 0) ln = ln.substring(0, ci); 10 ln = ln.trim(); 11 int n = ln.length(); 12 if (n != 0) { 13 if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) 14 fail(service, u, lc, "Illegal configuration-file syntax"); 15 int cp = ln.codePointAt(0); 16 if (!Character.isJavaIdentifierStart(cp)) 17 fail(service, u, lc, "Illegal provider-class name: " + ln); 18 for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { 19 cp = ln.codePointAt(i); 20 if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) 21 fail(service, u, lc, "Illegal provider-class name: " + ln); 22 } 23 if (!providers.containsKey(ln) && !names.contains(ln)) 24 names.add(ln); 25 } 26 return lc + 1; 27 }
parseLine方法就是讀該URL對應地檔案地一行,可以看到通過對“#”的位置判斷,忽略註釋,並且剔除空格,接著是一系列的引數合法檢驗,然後判斷providers和names裡是否都沒包含這個服務名稱,若都沒包含names直接add,最後返回下一行的行標;
當parse將所有內容讀取完畢,返回names.iterator()賦值給hasNextService中的pending。迴圈結束,獲取pending中的第一個元素賦值給nextName,返回true,hasNextService方法結束。
在nextService方法往下執行時,先用cn儲存nextName的值,再讓nextName=null,為下一次的遍歷做準備;接著通過類載入,載入名為cn的類,再通過該類例項化物件,並用providers快取起來,最後返回該例項物件。
其中cast方法是判斷物件是否合法:
1 public T cast(Object obj) { 2 if (obj != null && !isInstance(obj)) 3 throw new ClassCastException(cannotCastMsg(obj)); 4 return (T) obj; 5 }
至此ServiceLoader的迭代器的next方法結束。其hasNext方法與其類似,就不詳細分析了。
而其remove方法就更直接,直接丟擲異常來避免可能出現的危險情況:
1 public void remove() { 2 throw new UnsupportedOperationException(); 3 }
其中使用到的靜態fail方法只是丟擲異常:
1 private static void fail(Class<?> service, String msg, Throwable cause) 2 throws ServiceConfigurationError { 3 throw new ServiceConfigurationError(service.getName() + ": " + msg, 4 cause); 5 } 6 7 private static void fail(Class<?> service, String msg) 8 throws ServiceConfigurationError { 9 throw new ServiceConfigurationError(service.getName() + ": " + msg); 10 } 11 12 private static void fail(Class<?> service, URL u, int line, String msg) 13 throws ServiceConfigurationError { 14 fail(service, u + ":" + line + ": " + msg); 15 }
ServiceLoader除了load的兩個方法外還有個loadInstalled方法:
1 public static <S> ServiceLoader<S> loadInstalled(Class<S> service) { 2 ClassLoader cl = ClassLoader.getSystemClassLoader(); 3 ClassLoader prev = null; 4 while (cl != null) { 5 prev = cl; 6 cl = cl.getParent(); 7 } 8 return ServiceLoader.load(service, prev); 9 }
該方法與load方法不同在於loadInstalled使用的是擴充套件類載入器,而load使用的是傳入進來的或者是執行緒的上下文類載入器,其他都一樣。
ServiceLoader原始碼分析到此全部結束。
&n