深入剖析Spring boot自動(dòng)裝配原理
ServiceLoader是jdk6里面引進(jìn)的一個(gè)特性。它用來實(shí)現(xiàn)SPI(Service Provider Interface),一種服務(wù)發(fā)現(xiàn)機(jī)制,很多框架用它來做來做服務(wù)的擴(kuò)展發(fā)現(xiàn)。
系統(tǒng)里抽象的各個(gè)模塊一般會(huì)有很多種不同的實(shí)現(xiàn),如JDBC、日志等。通常模塊之間我們均是基于接口進(jìn)行編程,而不是對(duì)實(shí)現(xiàn)類進(jìn)行硬編碼。這時(shí)候就需要一種動(dòng)態(tài)替換發(fā)現(xiàn)的機(jī)制,即在運(yùn)行時(shí)動(dòng)態(tài)地給接口添加實(shí)現(xiàn),而不需要在程序中指明。
引用自JDK文檔對(duì)于java.util.ServiceLoader的描述:
服務(wù)是一個(gè)熟知的接口和類(通常為抽象類)集合。服務(wù)提供者是服務(wù)的特定實(shí)現(xiàn)。提供者中的類通常實(shí)現(xiàn)接口,并子類化在服務(wù)本身中定義的子類。服務(wù)提供者可以以擴(kuò)展的形式安裝在 Java 平臺(tái)的實(shí)現(xiàn)中,也就是將 jar 文件放入任意常用的擴(kuò)展目錄中。也可通過將提供者加入應(yīng)用程序類路徑,或者通過其他某些特定于平臺(tái)的方式使其可用。
為了加載,服務(wù)由單個(gè)類型表示,也就是單個(gè)接口或抽象類。一個(gè)給定服務(wù)的提供者包含一個(gè)或多個(gè)具體類,這些類擴(kuò)展了此服務(wù)類型,具有特定于提供者的數(shù)據(jù)和代碼。提供者類通常不是整個(gè)提供者本身而是一個(gè)代理,它包含足夠的信息來決定提供者是否能滿足特定請(qǐng)求,還包含可以根據(jù)需要?jiǎng)?chuàng)建實(shí)際提供者的代碼。提供者類的詳細(xì)信息高度特定于服務(wù);任何單個(gè)類或接口都不能統(tǒng)一它們,因此這里沒有定義任何這種類型。此設(shè)施唯一強(qiáng)制要求的是,提供者類必須具有不帶參數(shù)的構(gòu)造方法,以便它們可以在加載中被實(shí)例化。
當(dāng)服務(wù)的提供者提供了服務(wù)接口的一種實(shí)現(xiàn)之后,在jar包的META-INF/services/ 目錄里同時(shí)創(chuàng)建一個(gè)以服務(wù)接口命名的該服務(wù)接口具體實(shí)現(xiàn)類文件。當(dāng)外部程序裝配該模塊時(shí),通過該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類名,從而完成模塊的注入,而不需要在代碼里定制。
在JDBC中使用了ServiceLoader對(duì)不同數(shù)據(jù)庫驅(qū)動(dòng)進(jìn)行加載。
DriverManager通過ServiceLoader加載數(shù)據(jù)庫驅(qū)動(dòng),JDBC原理可移步【JDBC原理】。
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
ServiceLoader 使用小栗子
首先定義一個(gè)SPIService接口
package com.study;
public interface SPIService {
void excute();
}
再定義兩個(gè)實(shí)現(xiàn)類
package com.study.impl;
import com.study.SPIService;
public class SPIServiceImpl1 implements SPIService {
@Override
public void excute() {
System.out.println("SPIServiceImpl1");
}
}
package com.study.impl;
import com.study.SPIService;
public class SPIServiceImpl2 implements SPIService {
@Override
public void excute() {
System.out.println("SPIServiceImpl2");
}
}
在ClassPath路徑下配置添加文件,META-INF/services/com.study.SPIService,文件名為接口的全限定類名。在配置文件中加入兩個(gè)實(shí)現(xiàn)類的全限定類名。
com.study.impl.SPIServiceImpl1
com.study.impl.SPIServiceImpl2
寫一個(gè)測(cè)試類SPITest.java
public class SPITest {
public static void main(String[] args) {
ServiceLoader<SPIService> loaders = ServiceLoader.load(SPIService.class);
Iterator<SPIService> it = loaders.iterator();
while (it.hasNext()) {
SPIService spiSer= it.next();
spiSer.excute();
}
}
}
運(yùn)行結(jié)果:
SPIServiceImpl1
SPIServiceImpl2
ServiceLoader的load方法將在META-INF/services/com.study.SPIService中配置的子類都進(jìn)行了加載。
ServiceLoader的核心源碼分析
public final class ServiceLoader<S> implements Iterable<S>{
// 需要加載的資源的配置文件路徑
private static final String PREFIX = "META-INF/services/";
// 加載的服務(wù)類或接口
private Class<S> service;
// 類加載時(shí)用到的類加載器
private ClassLoader loader;
// 基于實(shí)例的已加載的順序緩存類,其中Key為實(shí)現(xiàn)類的全限定類名
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// "懶查找"迭代器,ServiceLoader的核心
private LazyIterator lookupIterator;
public void reload() {
// 清空緩存
providers.clear();
// 構(gòu)造LazyIterator實(shí)例
lookupIterator = new LazyIterator(service, loader);
}
// 私有構(gòu)造方法
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = svc;
loader = cl;
reload();
}
}
ServiceLoader只有一個(gè)私有的構(gòu)造函數(shù),也就是它不能通過構(gòu)造函數(shù)實(shí)例化,但是要實(shí)例化ServiceLoader必須依賴于它的靜態(tài)方法調(diào)用私有構(gòu)造去完成實(shí)例化操作。
來看ServiceLoader的提供的靜態(tài)方法,這幾個(gè)方法都可以用于構(gòu)造ServiceLoader的實(shí)例。
public static <S> ServiceLoader<S> load(Class<S> service) {
// 獲取當(dāng)前線程的線程上下文類加載器實(shí)例,確保通過此classLoader也能加載到項(xiàng)目中的資源文件
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);
}
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(Class<S> service, ClassLoader loader)是典型的靜態(tài)工廠方法,直接調(diào)用ServiceLoader的私有構(gòu)造器進(jìn)行實(shí)例化,除了需要指定加載類的目標(biāo)類型,還需要傳入類加載器的實(shí)例。load(Class<S>service)實(shí)際上也是委托到load(Class<S> service, ClassLoader loader),不過它使用的類加載器指定為線程上下文類加載器,一般情況下線程上下文類加載器獲取到的就是應(yīng)用類加載器(系統(tǒng)類加載器)。loadInstalled(Class<S> service)方法又看出了"雙親委派模型"的影子,它指定類加載器為最頂層的啟動(dòng)類加載器,最后也是委托到load(Class<S> service, ClassLoader loader)。
這里重點(diǎn)關(guān)注為什么類加載器使用線程上下文類加載器?以JDBC加載MySQL驅(qū)動(dòng)舉例。
Java類加載的過程通常是遵循雙親委派模型的。但是對(duì)于SPI接口實(shí)現(xiàn)類的加載就需要破壞雙親委派模型。
首先java.sql.DriverManager是由啟動(dòng)類加載器加載的,創(chuàng)建真正的Dirver對(duì)象時(shí)需要使用到mysql提供的實(shí)現(xiàn):com.mysql.jdbc.Dirver,即要初始化該類。但是啟動(dòng)類加載器加載DirverManager的時(shí)候,使用到了啟動(dòng)類加載器無法加載的類,這時(shí)候就需要由系統(tǒng)類加載器來加載。com.mysql.jdbc.Dirver通常放在類路徑下的(其實(shí)不一定)。到這里和線程上下文類加載器沒由任何關(guān)系。在DriverManager中使用系統(tǒng)類加載的時(shí)候,可以直接使用靜態(tài)方法ClassLoader.getSystemClassLoader(),但是這種情況的前提是com.mysql.jdbc.Dirver類在類路徑下。如果不在類路徑下,而且在系統(tǒng)環(huán)境中有其他的類加載器,在通過其他類加載器可能出現(xiàn)無法正確加載擴(kuò)展點(diǎn)的情況。比如某個(gè)類的字節(jié)碼是在數(shù)據(jù)庫中存儲(chǔ),這時(shí)我們需要自定義一個(gè)類加載器去加載它,這個(gè)類加載器會(huì)告訴DriverManager去我們指定放的地方取。因此Thread.currentThread().setContextClassLoader(自定義加載器/默認(rèn)是系統(tǒng)類加載器); 這個(gè)就是線程上下文類加載器起到的介質(zhì)作用。線程上下文中默認(rèn)放的是系統(tǒng)類加載器。
ServiceLoader實(shí)現(xiàn)了Iterable接口。
public Iterator<S> iterator() {
// Iterator的匿名實(shí)現(xiàn)
return new Iterator<S>() {
// 基于實(shí)例的已加載的順序緩存類Map的Entry迭代器實(shí)例
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
// 先從緩存中判斷是否有下一個(gè)實(shí)例,否則通過懶加載迭代器LazyIterator去判斷是否存在下一個(gè)實(shí)例
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
// 如果緩存中判斷是否有下一個(gè)實(shí)例,如果有則從緩存中的值直接返回,否則通過懶加載迭代器LazyIterator獲取下一個(gè)實(shí)例
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
// 不支持移除
public void remove() {
throw new UnsupportedOperationException();
}
};
}
LazyIterator本身也是一個(gè)Iterator接口的實(shí)現(xiàn),它是ServiceLoader的一個(gè)私有內(nèi)部類。
private class LazyIterator implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
// 加載的資源的URL集合
Enumeration<URL> configs = null;
// 所有需要加載的實(shí)現(xiàn)類的全限定類名的集合
Iterator<String> pending = null;
// 下一個(gè)需要加載的實(shí)現(xiàn)類的全限定類名
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
public boolean hasNext() {
// 如果下一個(gè)需要加載的實(shí)現(xiàn)類的全限定類名不為null,則說明資源中存在內(nèi)容
if (nextName != null) {
return true;
}
// 如果加載的資源的URL集合為null則嘗試進(jìn)行加載
if (configs == null) {
try {
// 資源的名稱,META-INF/services/ + '需要加載的類的全限定類名'
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 從ClassPath加載資源
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
// 從資源中解析出需要加載的所有實(shí)現(xiàn)類的全限定類名
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
// 獲取下一個(gè)需要加載的實(shí)現(xiàn)類的全限定類名
nextName = pending.next();
return true;
}
public S next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String cn = nextName;
nextName = null;
try {
// 反射構(gòu)造Class<S>實(shí)例,同時(shí)進(jìn)行初始化,并且強(qiáng)制轉(zhuǎn)化為對(duì)應(yīng)的類型的實(shí)例
S p = service.cast(Class.forName(cn, true, loader).newInstance());
// 添加進(jìn)緩存,Key為實(shí)現(xiàn)類的全限定類名,Value為實(shí)現(xiàn)類的實(shí)例
providers.put(cn, p);
return p;
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
} catch (Throwable x) {
fail(service, "Provider " + cn + " could not be instantiated: " + x, x);
}
throw new Error(); // This cannot happen
}
public void remove() {
throw new UnsupportedOperationException();
}
}
ServiceLoader總結(jié)
JDK提供了一種幫第三方實(shí)現(xiàn)者加載服務(wù)的便捷方式,如JDBC、日志等,第三方實(shí)現(xiàn)者需要遵循約定把具體實(shí)現(xiàn)的類名放在/META-INF里。當(dāng)JDK啟動(dòng)時(shí)會(huì)去掃描所有jar包里符合約定的類名,再調(diào)用forName進(jìn)行加載,如果JDK的ClassLoader無法加載,就使用當(dāng)前執(zhí)行線程的線程上下文類加載器。
但是在通過SPI查找服務(wù)的具體實(shí)現(xiàn)時(shí),會(huì)遍歷所有的實(shí)現(xiàn)進(jìn)行實(shí)例化,并在循環(huán)中找到需要的具體實(shí)現(xiàn)。
作者:java知路
歡迎關(guān)注微信公眾號(hào) :java知路