看了上篇spi使用后,你或許覺得spi太好用了吧,但或許也有疑問:
- 為什么只能放在
META-INF/services/目錄下?為什么要用全路徑命名? - 他的實現(xiàn)原理是什么?
基于這兩個問題,我們深入探究下ServiceLoader源碼。
構(gòu)造函數(shù)
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
// Android-changed: Do not use legacy security code.
// On Android, System.getSecurityManager() is always null.
// acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
- service 就是普通的
class類 - loader
ClassLoader類型變量,如果傳空就默認(rèn)ClassLoader.getSystemClassLoader
可以看到構(gòu)造函數(shù)里調(diào)用了reload()方法,且Android的 ClassLoader類沒有使用AccessController
reload()方法
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
初始化了兩個變量providers是用來緩存class的,lookupIterator是我們獲取子類繼承的核心處理類了,而 ClassLoader本身繼承了Iterable在iterator()方法里調(diào)用lookupIterator來實現(xiàn)重寫方法,ServiceLoader的操作都是通過該變量來實現(xiàn)的
iterator()
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
//首先檢查緩存,緩存沒有則從lookupIterator讀取
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
//首先檢查緩存,緩存沒有則從lookupIterator讀取
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
LazyIterator
LazyIterator是Iterator的繼承類,其兩個實現(xiàn)方法hasNext()和next()分別調(diào)用了hasNextService()和nextService()所以我們只要看這兩個方法就可以了
hasNextService()
private boolean hasNextService() {
//下一個繼承類的名字,不為空則直接返回true
if (nextName != null) {
return true;
}
//初始化配置
if (configs == null) {
try {
//PREFIX = "META-INF/services/";
//路徑全名稱為: "META-INF/services/" + 類的名稱
String fullName = PREFIX + service.getName();
//根據(jù)路徑獲取該接口類的配置文件,
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;
}
該方法就是解析我們在META-INF/services/目錄下配置的接口全路徑名的文件,讀取里面的繼承類文件名,來判斷當(dāng)前節(jié)點是否還有繼承類
這里就可以看到了我們的第一個問題:
- 為什么只能放在
META-INF/services/目錄下?通過PREFIX變量我們可以看到了原因,該變量定義了路徑的位置 - 為什么要用全路徑命名?因為
service.getName()獲取的就是全路徑名
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,
// Android-changed: Let the ServiceConfigurationError have a cause.
"Provider " + cn + " not found", x);
// "Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
// Android-changed: Let the ServiceConfigurationError have a cause.
ClassCastException cce = new ClassCastException(
service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
fail(service,
"Provider " + cn + " not a subtype", cce);
// 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
}
- 該方法首先調(diào)用
hasNextService()獲取當(dāng)前節(jié)點的繼承子類名稱,如果沒有會拋出異常 - 然后通過
Class.forName實例化該類 - 然后通過
isAssignableFrom校驗獲得到的類是否是service的子類 - 最后通過
cast強(qiáng)類型轉(zhuǎn)換為service類型,并添加到providers緩存里返回
整個LazyIterator的實現(xiàn)就介紹完了
這里就解釋了第二個問題ServiceLoader實現(xiàn)原理?他通過LazyIterator類獲取META-INF/services/目錄下接口對應(yīng)的文件,并讀取里面的繼承類名,然后通過類實例化返回,最終我們就可以獲取到了接口對應(yīng)實現(xiàn)的子類。