java開發(fā)ServiceLoader實現(xiàn)機制及SPI應(yīng)用
前言
做過java web開發(fā)的小伙伴大多數(shù)時候都需要鏈接數(shù)據(jù)庫,這個時候就需要配置數(shù)據(jù)庫引擎DriverClassName參數(shù),這樣我們的java應(yīng)用才能通過數(shù)據(jù)庫廠商給的Driver與指定的數(shù)據(jù)庫建立通信。但是這里就有一個疑問:
java.sql.Driver是jdk自帶的接口,它是由BoostrapClassLoader加載的,DriverClassName是外部廠商提供的具體實現(xiàn),是由AppClassLoader加載的,要建立與數(shù)據(jù)庫的通信,必然是通過java.sql.Driver接口方法發(fā)起的,那么在java.sql.Driver是如何拿到具體實現(xiàn)的呢?它是不是違背了ClassLoader的雙親委派模式呢?
如何繞過雙親委派模式
為了拿到AppClassLoader中加載的java.sql.Driver實現(xiàn)類,我們可以查看一下DriverManager是怎么處理的:
public static Driver getDriver(String url)
throws SQLException {
println("DriverManager.getDriver("" + url + "")");
ensureDriversInitialized();
......
}
private static void ensureDriversInitialized() {
......
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 核心代碼ServiceLoader
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try {
while (driversIterator.hasNext()) {
driversIterator.next();
}
} catch (Throwable t) {
// Do nothing
}
return null;
}
});
......
}
我們最終可以發(fā)現(xiàn),DriverManager通過ServiceLoader.load(Driver.class)就拿到了我們配置的DriverClassName實現(xiàn)類。這就實現(xiàn)在DriverManager中拿到了外部提供的Driver實現(xiàn),繞過來雙親委派模式。
ServiceLoader實現(xiàn)機制
我們來看一下ServiceLoader是如何實現(xiàn)SPI機制的,先從ServiceLoader.load()方法入手:
public static <S> ServiceLoader<S> load(Class<S> service) {
// 從當前線程中獲取ClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 創(chuàng)建一個ServiceLoader對象
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}
1.從當前線程中獲取ClassLoader;因為在創(chuàng)建AppClassLoader后,將AppClassLoader設(shè)置進當前線程的上下文中;
2.根據(jù)ClassLoader以及目標接口類創(chuàng)建一個ServiceLoader對象;
其實ServiceLoader核心代碼在hasNext()方法中:
@Override
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
最終都會調(diào)用到hasNextService()方法中:
private boolean hasNextService() {
// nextProvider默認為null,如果通過next()取出來了,nextProvider就會變成null
while (nextProvider == null && nextError == null) {
try {
// 找到目標實現(xiàn)類
Class<?> clazz = nextProviderClass();
if (clazz == null)
return false;
if (clazz.getModule().isNamed()) {
// ignore class if in named module
continue;
}
// 判斷service接口是否和clazz有父子關(guān)系
if (service.isAssignableFrom(clazz)) {
Class<? extends S> type = (Class<? extends S>) clazz;
// 獲取無參構(gòu)造函數(shù)
Constructor<? extends S> ctor
= (Constructor<? extends S>)getConstructor(clazz);
// 包裝成一個ProviderImpl對象
ProviderImpl<S> p = new ProviderImpl<S>(service, type, ctor, acc);
// 并賦值給nextProvider
nextProvider = (ProviderImpl<T>) p;
} else {
fail(service, clazz.getName() + " not a subtype");
}
} catch (ServiceConfigurationError e) {
nextError = e;
}
}
return true;
}
外部提供的實現(xiàn)類一定要有一個無參構(gòu)造函數(shù),否則會導(dǎo)致ServiceLoader加載失?。?/p>
我們下面再繼續(xù)深入看看ServiceLoader是怎么找到實現(xiàn)類的:
private Class<?> nextProviderClass() {
if (configs == null) {
try {
// 拼接文件名:"META-INF/services/接口名稱"
// 比如接口名為:java.sql.Driver,
// 那么文件路徑就是:"META-INF/services/java.sql.Driver"
String fullName = PREFIX + service.getName();
// 沒有指定ClassLoader,就通過getSystemClassLoader()加載目標文件
if (loader == null) {
configs = ClassLoader.getSystemResources(fullName);
} else if (loader == ClassLoaders.platformClassLoader()) {
// 如果是platformClassLoader,它沒有class path,那么看看BootLoader有沒有class path
if (BootLoader.hasClassPath()) {
configs = BootLoader.findResources(fullName);
} else {
configs = Collections.emptyEnumeration();
}
} else {
// 通過指定classLoader加載目標文件
configs = loader.getResources(fullName);
}
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
// 上面代碼只會執(zhí)行一次,這樣configs就不會為null,下次進來直接取下一個實現(xiàn)類
// 把configs內(nèi)容解析成一個迭代器
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return null;
}
pending = parse(configs.nextElement());
}
// 通過迭代器獲取下一個實現(xiàn)類名稱
String cn = pending.next();
try {
// 通過類名反射成Class對象
return Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
return null;
}
}
1.實現(xiàn)類的載入是因為在META-INF/services/文件夾中創(chuàng)建了以目標接口名稱命名的文件,并在里面寫上了實現(xiàn)類的全路徑類名。
2.ServiceLoader通過ClassLoader從class path中載入目標文件里面的內(nèi)容,并解析出實現(xiàn)類的全路徑類名;
3.最終通過反射的方式創(chuàng)建出實現(xiàn)類的Class對象,這樣就完成了SPI的實現(xiàn);
SPI在各個框架上的應(yīng)用
除了在數(shù)據(jù)庫Driver上使用了SPI,我們還可以發(fā)現(xiàn)SPI在各個框架上都有大量的應(yīng)用。比如我最近在看的Seata分布式事務(wù)框架,里面就有用到SPI:io.seata.common.loader.EnhancedServiceLoader

另一個就是我們經(jīng)常使用的mysql-connector-java以及阿里的Druid:


小結(jié)
通過以上源碼分析以及示例演示,我們簡單做一個小結(jié):
1.ServiceLoader打破雙親委派模式的方式通過獲取當前線程上下文中的ClassLoader完成的;
2.SPI的實現(xiàn)類名稱必須放在META-INF/services/文件夾下面,以目標接口名稱作為文件名稱,文件內(nèi)容為目標實現(xiàn)類全路徑類名;
3.目標實現(xiàn)類必須要有一個無參構(gòu)造函數(shù),否則SPI會失??;
以上就是java開發(fā)ServiceLoader實現(xiàn)機制及SPI應(yīng)用的詳細內(nèi)容,更多關(guān)于java開發(fā)ServiceLoader SPI的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot使用RestTemplate發(fā)送http請求的實操演示
RestTemplate是Spring 框架提供的 ,可用于在應(yīng)用中調(diào)用 rest 服務(wù),它簡化了與 http 服務(wù)的通信方式,統(tǒng)一了 RESTful 的標準,封裝了 http 鏈接,本文給大家介紹了SpringBoot使用RestTemplate發(fā)送http請求的實操演示,需要的朋友可以參考下2024-11-11
Java并行執(zhí)行任務(wù)的幾種方案小結(jié)
這篇文章主要介紹了Java并行執(zhí)行任務(wù)的幾種方案小結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
Spring Boot整合logback一個簡單的日志集成架構(gòu)
今天小編就為大家分享一篇關(guān)于Spring Boot整合logback一個簡單的日志集成架構(gòu),小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01

