Java SPI机制总结系列之万字详细图解SPI源码分析
原創(chuàng)/朱季謙
我在《Java SPI機制總結(jié)系列之開發(fā)入門實例》一文當(dāng)中,分享了Java SPI的玩法,但是這只是基于表面的應(yīng)用。若要明白其中的原理實現(xiàn),還需深入到底層源碼,分析一番。
這里再重溫一下SPI機制的概念:SPI,是Service Provider Interface的縮寫,即服務(wù)提供者接口,單從字面上看,可以這樣理解,該機制提供了一種可根據(jù)接口類型去動態(tài)加載出接口實現(xiàn)類對象的功能。打一個比喻,該機制就類似Spring容器,通過IOC將對象的創(chuàng)建交給Spring容器處理,若需要獲取某個類的對象,就從Spring容器里取出使用即可。同理,在SPI機制當(dāng)中,提供了一個類似Spring容器的角色,叫【服務(wù)提供者】,在代碼運行過程中,若要使用到實現(xiàn)了某個接口的服務(wù)實現(xiàn)類對象,只需要將對應(yīng)的接口類型交給服務(wù)提供者,服務(wù)提供者將會動態(tài)加載出所有實現(xiàn)了該接口的服務(wù)實現(xiàn)類對象,最后給到服務(wù)使用者使用。
接著前文的分享,可從以下三個步驟目錄去深入分析Java SPI機制源碼實現(xiàn)——
- 創(chuàng)建服務(wù)提供者ServiceLoader對象,其內(nèi)部生成一個可延遲加載接口對應(yīng)實現(xiàn)類對象的迭代器LazyIterator,主要作用是讀取并解析META-INF/services/目錄下的配置文件中service類名字,進而通過反射加載生成service類對象。
- 調(diào)用serviceLoader.iterator()返回一個內(nèi)部實際是調(diào)用LazyIterator迭代器的匿名迭代器對象。
- 遍歷迭代器,逐行解析接口全類名所對應(yīng)配置文件中的service實現(xiàn)類的名字,通過反射生成對象緩存到鏈表,最后返回。
//step 1 創(chuàng)建ServiceLoader對象,其內(nèi)部生成一個可延遲加載接口對應(yīng)實現(xiàn)類對象的迭代器LazyIterator,主要作用是讀取并解析META-INF/services/目錄下的配置文件中service類名字,進而通過反射加載生成service類對象。
ServiceLoader<UserService> serviceLoader = ServiceLoader.load(UserService.class);
//step 2 調(diào)用serviceLoader.iterator()返回一個內(nèi)部實際是調(diào)用LazyIterator迭代器的匿名迭代器對象。
Iterator<UserService> serviceIterator = serviceLoader.iterator();
//step 3 遍歷迭代器,逐行解析接口全類名所對應(yīng)配置文件中的service實現(xiàn)類的名字,通過反射生成對象緩存到鏈表,最后返回。
UserService service = serviceIterator.next();
service.getName();
}
}
整個過程這里先做一個全面概括——ServiceLoader類會延遲加載UserService接口全名對應(yīng)的META-INF/services/目錄下的配置文件com.zhu.service.UserService。當(dāng)找到對應(yīng)接口全名文件后,會逐行讀取文件里Class類名的字符串,假如存儲的是“com.zhu.service.impl.AUserServiceImpl”和“com.zhu.service.impl.BUserServiceImpl”這兩個類名,那么就會逐行取出,再通過反射【“Class類名”.newInstance()】,就可以創(chuàng)建出UserService接口對應(yīng)的服務(wù)提供者對象。這些對象會以結(jié)構(gòu)為<實現(xiàn)類名, 實現(xiàn)類對象>的Map形式,存儲到LinkedHashMap鏈表里。該鏈表將由迭代器循環(huán)遍歷,取出每一個實現(xiàn)類對象。
畫一個流程圖說明,大概如下——
接下來,基于該全貌流程圖,分別對源碼作分析。
一、創(chuàng)建服務(wù)提供者ServiceLoader對象,其內(nèi)部生成一個可延遲加載接口對應(yīng)實現(xiàn)類對象的迭代器LazyIterator,主要作用是讀取并解析META-INF/services/目錄下的配置文件中service類名字,進而通過反射加載生成service類對象。
先看第一部分代碼——
ServiceLoader<UserService> serviceLoader = ServiceLoader.load(UserService.class);
進入到ServiceLoader.load(UserService.class)方法里,里面基于當(dāng)前線程通Thread.currentThread().getContextClassLoader()創(chuàng)建一個當(dāng)前上下文的類加載器ClassLoader,該加載器在這里主要是用來加載META-INF.services目錄下的文件。
在load方法里,將UserService.class和類加載器ClassLoader當(dāng)作參數(shù),交給ServiceLoader中的另一個重載方法ServiceLoader.load(service, cl)去做進一步具體實現(xiàn)。
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
進入到ServiceLoader.load(service, cl),該方法里創(chuàng)建了一個ServiceLoader對象,該對象默認執(zhí)行了參數(shù)值分別為UserService.class和ClassLoader的帶參構(gòu)造方法。
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
根據(jù)字面意義,可以看出,ServiceLoader是一個專門負責(zé)加載服務(wù)的對象,在SPI機制里,它充當(dāng)專門提供接口實現(xiàn)服務(wù)對象的角色。
這里就有兩個問題,它怎么提供服務(wù)對象,它提供的是哪個接口的服務(wù)?
針對這兩個問題,基于傳進來的參數(shù)值UserService.class和類加載器ClassLoader,就已經(jīng)能猜出答案里,它將通過類加載器ClassLoader去加載實現(xiàn)UserService接口的具體服務(wù)類對象。
進入到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;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
這里暫時只需要關(guān)注loader和 reload(),而acc是專門用在服務(wù)實現(xiàn)類的安全權(quán)限訪問方面的,本文暫未涉及到acc,后續(xù)會考慮專門寫一篇文分享下SPI下,如何實現(xiàn)服務(wù)實現(xiàn)類的安全權(quán)限訪問。
傳進來的loader如果為空,那么就使用ClassLoader.getSystemClassLoader(),即系統(tǒng)類加載器,可以簡單理解,無論如何,都會得到一個非空的類加載器。
接著進入到reload()方法里——
/**
* Clear this loader's provider cache so that all providers will be reloaded.
* 清除此加載器的提供程序緩存,以便重新加載所有提供程序。
* <p> After invoking this method, subsequent invocations of the {@link
* #iterator() iterator} method will lazily look up and instantiate providers from scratch,
just as is done by a newly-created loader.
調(diào)用此方法后,后續(xù)調(diào)用{@link #iterator() iterator}方法將從零開始惰性查找并實例化提供商,
就像新創(chuàng)建的加載器一樣。
*
* <p> This method is intended for use in situations in which new providers
* can be installed into a running Java virtual machine.
此方法旨在用于新提供者可以安裝到正在運行的Java虛擬機中。
*/
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
根據(jù)reload() 方法的注釋說明,可以看到,該方法做了兩件事:
- providers是一個Map結(jié)構(gòu)的鏈表LinkedHashMap,專門存儲服務(wù)實例(在這里是存儲UserService接口實現(xiàn)類對象)的集合,通過clear()方法做了清除,即清空了里面的所有記錄。
- LazyIterator實現(xiàn)了Iterator迭代器接口,根據(jù)類名可以看出,這是一個Lazy懶加載形式的迭代器。
需要額外解釋一下延遲加載是什么意思。延遲加載,說明項目啟動時不會立馬加載,而是需要被用到的時候,才會動態(tài)去加載。實現(xiàn)了Iterator迭代器接口的LazyIterator對象,就具備延遲加載的功能。
簡單看一下,該LazyIterator的結(jié)構(gòu)——
private class LazyIterator implements Iterator<S>
{
//存儲服務(wù)接口的Class類型
Class<S> service;
//存儲類加載器。
ClassLoader loader;
//存儲服務(wù)接口全類名所對應(yīng)在META-INF.services目錄中的配置文件資源路徑
Enumeration<URL> configs = null;
//存儲里配置文件中服務(wù)類名的迭代器
Iterator<String> pending = null;
//存儲下一個返回的服務(wù)提供者類名
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
......
}
總結(jié)這部分源碼,主要是創(chuàng)建一個可加載接口服務(wù)提供者實例的ServiceLoader類對象,其內(nèi)部創(chuàng)建一個具有延遲加載功能的迭代器LazyIterator。該LazyIterator迭代器能夠延遲去逐行遍歷解析出接口全類名所對應(yīng)配置文件中的Class類名字符串,再將Class類名字符串通過反射生成服務(wù)提供者對象,存儲到鏈表,用于外部迭代遍歷。
接下來,會基于該延遲加載LazyIterator迭代器,做進一步處理。
到目前為止,只是在ServiceLoader類對象的內(nèi)部,創(chuàng)建了一個存儲接口UserService.class,類加載器loader的LazyIterator迭代器,暫時還沒涉及到如何獲取接口對應(yīng)的服務(wù)提供者。
簡單理解成,菜刀和鍋都準備好了,就等切菜和煮菜了。
二、調(diào)用serviceLoader.iterator()返回一個內(nèi)部實際是調(diào)用LazyIterator迭代器的匿名迭代器對象
這里通過serviceLoader.iterator()得到了一個類型為UserService的迭代器。
Iterator<UserService> serviceIterator = serviceLoader.iterator();
先進入到serviceLoader.iterator()內(nèi)部——
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();
}
};
}
該方法里,return new Iterator() { ... }表示創(chuàng)建一個實現(xiàn)了Iterator接口的匿名內(nèi)部類實例對象,并返回該實例對象作為一個迭代器。
至于這個匿名對象是叫張三還是李四,都不重要。重要的是,其內(nèi)部具有能被外部正常調(diào)用的hasNext()和next()就可以了。
我畫了一幅簡單的漫畫,舉例說明一下,這里為何可以直接返回一個實現(xiàn)Iterator接口的匿名內(nèi)部類實例對象。
故事是這樣的,有一個老板,想要招一個工具人,哦,不對,是打工人(反正都一樣......)——
故事到這里就結(jié)束了,這個return new Iterator() { ... }返回的匿名內(nèi)部類,就像無數(shù)籍籍無名的底層打工人一樣,或許自始自終都無人知道他們的名字,但他們用自己辛勤的手(hasNext()方法)腳(next()方法),在平凡的崗位上,默默做著不平凡的工作,提供著可以幫助其他人(服務(wù)使用者)的服務(wù)。
接下來,讓我們看看這些打工人那布滿皺紋的手和腳——
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();
}
knownProviders是一個包裝了LinkedHashMap providers = new LinkedHashMap<>()鏈表的迭代器。
當(dāng)調(diào)用hasNext()或者next()時,都會判斷providers里是否還有可以遍歷獲取的值,如果空了,就會調(diào)用lookupIterator.hasNext()或者lookupIterator.next()。
這個lookupIterator,正是前文創(chuàng)建的LazyIterator迭代器對象的引用。
匿名迭代器對象中的這兩個方法,分別是以下兩種功能:
- hasNext()判斷迭代器是否存在下一個元素。
- next()獲取迭代器中的下一個元素。
可見,這部分源碼調(diào)用serviceLoader.iterator()返回一個提供hasNext()和next()方法的匿名迭代器對象,實際上,hasNext()和next()方法內(nèi)真實調(diào)用的是迭代器LazyIterator的hasNext()和next()方法。
三、遍歷迭代器,逐行解析接口全類名所對應(yīng)配置文件中的service實現(xiàn)類的名字,通過反射生成對象緩存到鏈表,最后返回。
該分析最后的代碼了,這里已經(jīng)到遍歷循環(huán)迭代器,通過serviceIterator.next()取出存儲接口服務(wù)提供者對象——
while (serviceIterator.hasNext()) {
UserService service = serviceIterator.next();
service.getName();
}
}
這里的hasNext()和next(),正是前文return new Iterator() { ... }匿名對象里的hasNext()和next()方法。故而在執(zhí)行serviceIterator.hasNext()或者serviceIterator.next(),將跳轉(zhuǎn)到#ServiceLoader類#iterator() 中,執(zhí)行該匿名內(nèi)部類的hasNext()和next()方法。
先來看hasNext()方法——
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
若是第一次執(zhí)行時,knownProviders迭代器里的LinkedHashMap鏈表必定是空的,這時候,就會執(zhí)行l(wèi)ookupIterator.hasNext()——
public boolean hasNext() {
if (acc == null) {
//acc為空,執(zhí)行的是這一步代碼
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
這里acc為空,故而執(zhí)行的是return hasNextService()語句——
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//"META-INF/services/" + 接口全類名
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
//執(zhí)行該行代碼
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;
}
初次調(diào)用,configs是null,而類加載器loader非空,故而會執(zhí)行configs = loader.getResources(fullName)這行代碼。
基于該執(zhí)行步驟,分析一下這里的configs作用是什么,先看以下兩個邏輯——
- PREFIX的值為private static final String PREFIX = "META-INF/services/",表示正是目錄META-INF/services/路徑。
- service.getName()是獲取Class的name值,我們傳進來的是UserService.class,故而這里service.getName()獲取到的,便是接口全名com.zhu.service.UserService。
兩者結(jié)合,即代碼String fullName = PREFIX + service.getName()得到的,便是“METAINF/services/com.zhu.service.UserService”字符串,表示文件路徑名。
這時候,我們的類加載器就開始派上用場了——
configs = loader.getResources(fullName);
沒錯,到這里已經(jīng)拿到UserService接口全類名對應(yīng)的文件路徑,就可以通過類加載器讀取到該文件資源了。
讀取到該文件之后,之后就可以解析存放在文件里的接口的服務(wù)實現(xiàn)類信息了,故而具體實現(xiàn)在pending =parse(service, configs.nextElement())這行代碼里——
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
//逐行解析讀取配置文件類名,將讀取到的類名存儲到ArrayList,最后包裝成iterator返回賦值給pending
pending = parse(service, configs.nextElement());
}
進入到parse方法里,可以看到,這里開始通過while((lc =parseLine(service, u, r, lc, names))>=0)對文件內(nèi)容逐行讀取,同時創(chuàng)建一個ArrayList
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();
}
進入到parseLine(service, u, r, lc, names))方法,代碼String ln = r.readLine()表示讀取出文件每一行的字符串賦值給ln。
若遇到有#注釋符號的就跳過,只讀取非#號注釋的類名字符串,以names.add(ln)保存到一個ArrayList里。
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);
}
//讀取文件里的類名字符串存儲到names這個ArrayList里
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}
將讀取文件里的類名存到ArrayList后,最后return names.iterator()返回一個iterator迭代器,可debug打印看一下,可以看到該ArrayList緩存了從文件里讀取出來的類名——
該迭代器在解析完成后,會執(zhí)行一次nextName = pending.next(),表示通過迭代器方式取出ArrayList中的第一個字符串,即“com.zhu.service.impl.AUserServiceImpl”,同時return true。
這里nextName = pending.next()和return true就呼應(yīng)了外部服務(wù)使用者的調(diào)用,可見serviceIterator.hasNext()內(nèi)部,若迭代器下一個元素不為空,那么就將下一個元素通過取出,賦值給nextName,同時返回true,讓while循環(huán)正常遍歷下去——
前面的nextName = pending.next()將會在serviceIterator.next()里有所體現(xiàn)。
接下來,在next()中,第一次調(diào)用,也是lookupIterator.next()方法——
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
進入到lookupIterator.next()方法——
public S next() {
if (acc == null) {
//執(zhí)行該方法
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
同樣,實現(xiàn)的是nextService()——
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
/**
*nextName即將前文的com.zhu.service.impl.AUserServiceImpl
*String cn = nextName
*通過Class.forName(cn, false, loader),即可生成AUserServiceImpl的Class類對象
*/
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 {
//既然已經(jīng)拿到AUserServiceImpl的Class類對象,通過反射c.newInstance()便能生成相應(yīng)對象
S p = service.cast(c.newInstance());
//生成的對象會以結(jié)構(gòu)為<實現(xiàn)類名, 實現(xiàn)類對象>的Map形式,存儲到LinkedHashMap鏈表里
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
在這里面,主要做了這樣幾件事:
- 將nextName字符串賦值給cn,首次調(diào)用時,這里的nextName值為“com.zhu.service.impl.AUserServiceImpl”;
- 通過 c = Class.forName(cn,false, loader)生成AUserServiceImpl類的Class對象;
- 通過反射通過c.newInstance()生成AUserServiceImpl類實例對象;
- 生成的對象會以結(jié)構(gòu)為<實現(xiàn)類名, 實現(xiàn)類對象>的Map形式,存儲到LinkedHashMap鏈表里;
- 將生成的對象返回;
因此,在第一次調(diào)用完UserService service = serviceIterator.next()后,就能拿到了接口UserService的第一個實現(xiàn)類對象com.zhu.service.impl.AUserServiceImpl,進而就可以執(zhí)行相應(yīng)的重寫方法service.getName()。
到while的第二次遍歷時,執(zhí)行serviceIterator.hasNext()后,會取出ArrayList中的第二個緩存類名“com.zhu.service.impl.BUserServiceImpl”賦值給nextName,這樣在執(zhí)行UserService service = serviceIterator.next()時,就會重復(fù)執(zhí)行nextService()里的邏輯。一直迭代遍歷,直到將配置里的類名都遍歷完,serviceIterator才最終結(jié)束該UserService接口的服務(wù)提供功能。
首次調(diào)用就是以上流程,值得提的一個地方是,在反射創(chuàng)建完成的對象后,將以結(jié)構(gòu)為<實現(xiàn)類名, 實現(xiàn)類對象>的Map形式。存儲到LinkedHashMap鏈表里。
這個LinkedHashMap鏈表緩存的作用是什么呢?
這時回頭去看下這行代碼,還記得它里面創(chuàng)建了一個匿名內(nèi)部類嗎——
這個匿名內(nèi)部類里,其hasNext()和next()方法,會判斷knownProviders是否為空,不為空才去調(diào)用knownProviders里的方法。
這里的knownProviders正是使用到了LinkedHashMap鏈表緩存里的對象。
這個鏈表的作用,就是方便出現(xiàn)重復(fù)創(chuàng)建一個匿名迭代器去后去獲取接口的服務(wù)對象時,直接從LinkedHashMap鏈表緩存里讀取即可,無需再次去解析接口對應(yīng)的配置文件,起到了查詢優(yōu)化的作用。
類似這樣的場景,第二次生成一個迭代器去提供接口的服務(wù)功能時,就直接從從LinkedHashMap鏈表緩存里讀取了。
以上,就是Java SPI的完整源碼分析。
總結(jié)
以上是生活随笔為你收集整理的Java SPI机制总结系列之万字详细图解SPI源码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 历时三年,写的一本数据结构与算法pdf,
- 下一篇: Java -- Stream流用法