java開發(fā)ServiceLoader實(shí)現(xiàn)機(jī)制及SPI應(yīng)用
前言
做過java web開發(fā)的小伙伴大多數(shù)時(shí)候都需要鏈接數(shù)據(jù)庫,這個(gè)時(shí)候就需要配置數(shù)據(jù)庫引擎DriverClassName
參數(shù),這樣我們的java應(yīng)用才能通過數(shù)據(jù)庫廠商給的Driver
與指定的數(shù)據(jù)庫建立通信。但是這里就有一個(gè)疑問:
java.sql.Driver
是jdk自帶的接口,它是由BoostrapClassLoader加載的,DriverClassName
是外部廠商提供的具體實(shí)現(xiàn),是由AppClassLoader加載的,要建立與數(shù)據(jù)庫的通信,必然是通過java.sql.Driver
接口方法發(fā)起的,那么在java.sql.Driver
是如何拿到具體實(shí)現(xiàn)的呢?它是不是違背了ClassLoader的雙親委派模式呢?
如何繞過雙親委派模式
為了拿到AppClassLoader中加載的java.sql.Driver
實(shí)現(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
實(shí)現(xiàn)類。這就實(shí)現(xiàn)在DriverManager
中拿到了外部提供的Driver
實(shí)現(xiàn),繞過來雙親委派模式。
ServiceLoader實(shí)現(xiàn)機(jī)制
我們來看一下ServiceLoader
是如何實(shí)現(xiàn)SPI機(jī)制的,先從ServiceLoader.load()
方法入手:
public static <S> ServiceLoader<S> load(Class<S> service) { // 從當(dāng)前線程中獲取ClassLoader ClassLoader cl = Thread.currentThread().getContextClassLoader(); // 創(chuàng)建一個(gè)ServiceLoader對象 return new ServiceLoader<>(Reflection.getCallerClass(), service, cl); }
1.從當(dāng)前線程中獲取ClassLoader;因?yàn)樵趧?chuàng)建AppClassLoader后,將AppClassLoader設(shè)置進(jìn)當(dāng)前線程的上下文中;
2.根據(jù)ClassLoader以及目標(biāo)接口類創(chuàng)建一個(gè)ServiceLoader對象;
其實(shí)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默認(rèn)為null,如果通過next()取出來了,nextProvider就會變成null while (nextProvider == null && nextError == null) { try { // 找到目標(biāo)實(shí)現(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); // 包裝成一個(gè)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; }
外部提供的實(shí)現(xiàn)類一定要有一個(gè)無參構(gòu)造函數(shù),否則會導(dǎo)致ServiceLoader加載失敗;
我們下面再繼續(xù)深入看看ServiceLoader
是怎么找到實(shí)現(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()加載目標(biāo)文件 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加載目標(biāo)文件 configs = loader.getResources(fullName); } } catch (IOException x) { fail(service, "Error locating configuration files", x); } } // 上面代碼只會執(zhí)行一次,這樣configs就不會為null,下次進(jìn)來直接取下一個(gè)實(shí)現(xiàn)類 // 把configs內(nèi)容解析成一個(gè)迭代器 while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return null; } pending = parse(configs.nextElement()); } // 通過迭代器獲取下一個(gè)實(shí)現(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.實(shí)現(xiàn)類的載入是因?yàn)樵?code>META-INF/services/文件夾中創(chuàng)建了以目標(biāo)接口名稱命名的文件,并在里面寫上了實(shí)現(xiàn)類的全路徑類名。
2.ServiceLoader通過ClassLoader從class path中載入目標(biāo)文件里面的內(nèi)容,并解析出實(shí)現(xiàn)類的全路徑類名;
3.最終通過反射的方式創(chuàng)建出實(shí)現(xiàn)類的Class對象,這樣就完成了SPI的實(shí)現(xiàn);
SPI在各個(gè)框架上的應(yīng)用
除了在數(shù)據(jù)庫Driver
上使用了SPI
,我們還可以發(fā)現(xiàn)SPI
在各個(gè)框架上都有大量的應(yīng)用。比如我最近在看的Seata分布式事務(wù)框架,里面就有用到SPI
:io.seata.common.loader.EnhancedServiceLoader
另一個(gè)就是我們經(jīng)常使用的mysql-connector-java以及阿里的Druid:
小結(jié)
通過以上源碼分析以及示例演示,我們簡單做一個(gè)小結(jié):
1.ServiceLoader打破雙親委派模式的方式通過獲取當(dāng)前線程上下文中的ClassLoader完成的;
2.SPI
的實(shí)現(xiàn)類名稱必須放在META-INF/services/
文件夾下面,以目標(biāo)接口名稱作為文件名稱,文件內(nèi)容為目標(biāo)實(shí)現(xiàn)類全路徑類名;
3.目標(biāo)實(shí)現(xiàn)類必須要有一個(gè)無參構(gòu)造函數(shù),否則SPI
會失??;
以上就是java開發(fā)ServiceLoader實(shí)現(xiàn)機(jī)制及SPI應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于java開發(fā)ServiceLoader SPI的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
LambdaQueryWrapper與QueryWrapper的使用方式
這篇文章主要介紹了LambdaQueryWrapper與QueryWrapper的使用方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05Java 線程對比(Thread,Runnable,Callable)實(shí)例詳解
這篇文章主要介紹了Java 線程(Thread,Runnable,Callable)實(shí)例詳解的相關(guān)資料,這里對java 線程的三種方法進(jìn)行了對比,需要的朋友可以參考下2016-12-12java設(shè)計(jì)模式學(xué)習(xí)之裝飾模式
這篇文章主要為大家詳細(xì)介紹了java設(shè)計(jì)模式學(xué)習(xí)之裝飾模式的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10@DS注解的使用,動態(tài)數(shù)據(jù)源,事務(wù)詳解
在項(xiàng)目中使用多數(shù)據(jù)源時(shí),可以借助苞米豆的dynamic-datasource-spring-boot-starter進(jìn)行配置,首先需引入相應(yīng)的jar包,并在application.yml中設(shè)置主從數(shù)據(jù)源,其中一般選擇master作為默認(rèn)數(shù)據(jù)源,在實(shí)現(xiàn)類中通過@DS注解指定數(shù)據(jù)源2024-09-09SpringBoot中支持Https協(xié)議的實(shí)現(xiàn)
本文主要介紹了SpringBoot中支持Https協(xié)議的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01Java基礎(chǔ)學(xué)習(xí)之標(biāo)簽
在Java中,標(biāo)簽必須在循環(huán)之前使用, 一個(gè)循環(huán)之中嵌套另一個(gè)循環(huán)的開關(guān),從多重嵌套中continue或break,該文詳細(xì)介紹了標(biāo)簽的相關(guān)知識,對正在學(xué)習(xí)java基礎(chǔ)的小伙伴們還很有幫助,需要的朋友可以參考下2021-05-05