ServiceLoader主要的功能是用來完成對SPI的provider的加載。sql
先看下它的成員:數組
public final class ServiceLoader<S>
implements Iterable<S> {
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;
......
}
複製代碼
能夠看到他首先是實現了Iterable接口,能夠迭代。緩存
其構造方法是一個private方法,不對外提供,在使用時咱們須要調用其靜態的load方法,由其自身產生ServiceLoader對象:安全
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);
}
複製代碼
能夠看到對load方法進行了重載,其中參數service是要加載的類;單參方法沒有類加載器,使用的是當前線程的類加載器;最後調用的是雙參的load方法;而雙參的load方法也很簡單,只是直接調用ServiceLoader的構造方法,實例化了一個對象。bash
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();
複製代碼
能夠看到其構造方法邏輯依舊很簡單,首先是判斷傳入的svc(即傳入的service)是否爲空,如果爲空直接報異常,不然給service 成員賦值:架構
public static <T> T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
複製代碼
而後給進行cl的非空判斷,給loader 成員賦值;接着給acc 成員賦值,其根據是否設置了安全管理器SecurityManager來賦值;最後調用reload方法。併發
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
複製代碼
能夠看到reload方法是一個public方法,那麼在每次調用reload時就須要將以前加載的清空掉,因此直接使用providers這個map的clear方法清空掉緩存;接着使用剛纔賦值後的service和loader產生一個LazyIterator對象賦值給lookupIterator成員。app
LazyIterator是ServiceLoader的內部類,其定義以下:分佈式
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;
}
......
}
複製代碼
這裏就能夠看到ServiceLoader的實際加載過程就交給了LazyIterator來作,將ServiceLoader的service和loader成員分別賦值給了LazyIterator的service和loader成員。 configs是服務的URL枚舉; pending是保存要加載的服務的名稱集合; nextName是下一個要加載的服務名稱;ide
ServiceLoader實現了Iterable接口,其實現的iterator方法以下:
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();
}
}
;
}
複製代碼
能夠看到它是直接建立了一個Iterator對象返回;其knownProviders成員直接獲取providers的entrySet集合的迭代器;在hasNext和next方法中咱們能夠看到,它是先經過判斷knownProviders裏有沒有(即providers),若沒有再去lookupIterator中找; 前面咱們能夠看到providers裏並沒用put任何東西,那麼就說明put操做也是在lookupIterator中完成的。
先看到lookupIterator的next方法:
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);
}
}
複製代碼
首先根據判斷acc是否爲空,若爲空則說明沒有設置安全策略直接調用nextService方法,不然以特權方式調用nextService方法。
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
}
複製代碼
首先根據hasNextService方法判斷,若爲false直接拋出NoSuchElementException異常,不然繼續執行。
hasNextService方法:
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;
}
複製代碼
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方法:
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();
}
複製代碼
能夠看到parse方法直接經過URL打開輸入流,經過parseLine一行一行地讀取將結果保存在names數組裏。
parseLine方法:
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;
}
複製代碼
parseLine方法就是讀該URL對應地文件地一行,能夠看到經過對「#」的位置判斷,忽略註釋,而且剔除空格,接着是一系列的參數合法檢驗,而後判斷providers和names裏是否都沒包含這個服務名稱,若都沒包含names直接add,最後返回下一行的行標;
當parse將全部內容讀取完畢,返回names.iterator()賦值給hasNextService中的pending。循環結束,獲取pending中的第一個元素賦值給nextName,返回true,hasNextService方法結束。
在nextService方法往下執行時,先用cn保存nextName的值,再讓nextName=null,爲下一次的遍歷作準備;接着經過類加載,加載名爲cn的類,再經過該類實例化對象,並用providers緩存起來,最後返回該實例對象。
其中cast方法是判斷對象是否合法:
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException(cannotCastMsg(obj));
return (T) obj;
}
複製代碼
至此ServiceLoader的迭代器的next方法結束。其hasNext方法與其相似,就不詳細分析了。
而其remove方法就更直接,直接拋出異常來避免可能出現的危險狀況:
public void remove() {
throw new UnsupportedOperationException();
}
複製代碼
其中使用到的靜態fail方法只是拋出異常:
private static void fail(Class<?> service, String msg, Throwable cause)
throws ServiceConfigurationError {
throw new ServiceConfigurationError(service.getName() + ": " + msg,
cause);
}
private static void fail(Class<?> service, String msg)
throws ServiceConfigurationError {
throw new ServiceConfigurationError(service.getName() + ": " + msg);
}
private static void fail(Class<?> service, URL u, int line, String msg)
throws ServiceConfigurationError {
fail(service, u + ":" + line + ": " + msg);
}
複製代碼
ServiceLoader除了load的兩個方法外還有個loadInstalled方法:
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);
}
複製代碼
該方法與load方法不一樣在於loadInstalled使用的是擴展類加載器,而load使用的是傳入進來的或者是線程的上下文類加載器,其餘都同樣。
ServiceLoader源碼分析到此所有結束。
最後,歡迎作Java的工程師朋友們加入Java高級架構進階Qqun:963944895
羣內有技術大咖指點難題,還提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)
比你優秀的對手在學習,你的仇人在磨刀,你的閨蜜在減肥,隔壁老王在練腰, 咱們必須不斷學習,不然咱們將被學習者超越!
趁年輕,使勁拼,給將來的本身一個交代!