Java和Dubbo的SPI機(jī)制原理解析
SPI: 簡(jiǎn)單理解就是,你一個(gè)接口有多種實(shí)現(xiàn),然后在代碼運(yùn)行時(shí)候,具體選用那個(gè)實(shí)現(xiàn),這時(shí)候我們就可以通過一些特定的方式來告訴程序?qū)び媚莻€(gè)實(shí)現(xiàn)類,這就是SPI。
JAVA的SPI
全稱為 Service Provider Interface,是一種服務(wù)發(fā)現(xiàn)機(jī)制。它是約定在 Classpath 下的 META-INF/services/ 目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件,然后文件里面記錄的是此 jar 包提供的具體實(shí)現(xiàn)類的全限定名。
這樣當(dāng)我們引用了某個(gè) jar 包的時(shí)候就可以去找這個(gè) jar 包的 META-INF/services/ 目錄,再根據(jù)接口名找到文件,然后讀取文件里面的內(nèi)容去進(jìn)行實(shí)現(xiàn)類的加載與實(shí)例化。
例如:
java的jdbc就使用了SPI機(jī)制,當(dāng)我項(xiàng)目種應(yīng)用了mysql的連接jar時(shí)候,就會(huì)去去mysql-connector-java.jar下的META-INF/services/ 目錄查找java.sql.Driver名的文件,然后加載里面全類名的類。如果使用oracle連接驅(qū)動(dòng)時(shí)候,就會(huì)去ojdbc.jar下面去找java.sql.Driver文件里的配置的全類名。
并且通過IDEA的智能提示功能,也能看到,在你切換不同連接的jar包時(shí)候,Driver接口實(shí)現(xiàn)類是不同的。
使用mysql的連接驅(qū)動(dòng):
切換到oracle的連接驅(qū)動(dòng):
Java的SPI機(jī)制源碼分析
下面這段代碼,以jdbc的SPI為例,可以作為debug的入口:
package com.example.demo; import java.sql.Connection; import java.sql.DriverManager; /** * @author:luzaichun * @Date:2021/3/14 * @Time:14:09 **/ public class JDBCMain { private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=true&useUnicode=true&characterEncoding=UTF-8"; public static void main(String[] args) throws Exception{ Connection conn = DriverManager.getConnection(URL, "root", "123456"); } }
在使用DriverManager.getConnection()方法時(shí)候,會(huì)加載并初始化DriverManager類,此類是jdbc使用SPI的核心類。
1.DriverManager類初始化,調(diào)用static代碼塊,執(zhí)行DriverManager#loadInitialDrivers()方法
2.使用javaSPI的核心類ServiceLoader#load()和以及其內(nèi)部實(shí)現(xiàn)了Iterator的LazyIterator#hasNext()和
LazyIterator#next(),加載接口的具體實(shí)現(xiàn)類。
ServiceLoader.load()整個(gè)代碼流程,如下圖。其實(shí)就是給LazyIterator類的賦值屬性,是那個(gè)接口要進(jìn)行SPI,使用的類加載器是哪一個(gè)。
driversIterator.hasNext()和driversIterator.next()方法負(fù)責(zé)類實(shí)際類的加載
- driversIterator.hasNext()最后實(shí)際是調(diào)到了LazyIterator.hasNext();
- driversIterator.next()最后實(shí)際是調(diào)到了LazyIterator.next();
hashNext()方法讀到SPI的配置文件里的全類名
next()方法最后通過反射創(chuàng)建出具體實(shí)現(xiàn)類的實(shí)例
總結(jié):
- jdbc的SPI,通過DriverManager類靜態(tài)代碼塊執(zhí)行l(wèi)oadInitialDrivers()方法
- 然后通過ServiceLoader.load()拿到具體的接口,以及類加載器。
- 通過實(shí)現(xiàn)了Iterator類的LazyIterator類的hasNext方法讀取配置文件,拿到接口的具體實(shí)現(xiàn)全類名
- 在next()方法內(nèi)部,通過反射機(jī)制,由實(shí)現(xiàn)類的全類名,加載具體實(shí)現(xiàn)類。
代碼實(shí)戰(zhàn)java SPI
DemoService接口
public interface DemoService { String sayHello(String msg); }
XiaoHongDemoServiceImpl實(shí)現(xiàn)類
public class XiaoHongDemoServiceImpl implements DemoService { @Override public String sayHello(String msg) { return "xiaohong:"+msg; } }
ZhangSanDemoServiceImpl實(shí)現(xiàn)類
public class ZhangSanDemoServiceImpl implements DemoService { @Override public String sayHello(String msg) { return "zhangsan:"+msg; } }
定義SPI配置文件
最后使用
public class DemoMain { public static void main(String[] args) { ServiceLoader<DemoService> serviceLoad = ServiceLoader.load(DemoService.class); Iterator<DemoService> iterator = serviceLoad.iterator(); while (iterator.hasNext()){ DemoService demoService = iterator.next(); String returnStr = demoService.sayHello("lzc賊帥?。。?!"); System.out.println(returnStr); } } }
執(zhí)行結(jié)果:
java SPI劣勢(shì),會(huì)加載SPI配置文件里定義的所有配置類,如果用不上該類,也會(huì)加載。通俗點(diǎn)講,就是無法按需加載。
Dubbo的SPI
dubbo SPI使用
需要先引入dubbo相關(guān)的依賴
1.定義接口
通過dubbo的SPI注解標(biāo)注定義的接口
@SPI("xiaohong") public interface DubboSPIService { void sayHello(); }
2.多個(gè)實(shí)現(xiàn)類
public class XiaoHongDubboSPIServiceImpl implements DubboSPIService { @Override public void sayHello() { System.out.println("小紅說:lzc賊帥!"); } }
public class XiaoMingDubboSPIServiceImpl implements DubboSPIService { @Override public void sayHello() { System.out.println("小明說:lzc賊帥!"); } }
3.定義dubbo SPI配置文件
META-INF/dubbo目錄下定義接口全類名的文件,配置key-value的實(shí)現(xiàn)
Dubbo 對(duì)配置文件目錄的約定,不同于 Java SPI ,Dubbo 分為了三類目錄。
META-INF/services/ 目錄:該目錄下的 SPI 配置文件是為了用來兼容 Java SPI 。
META-INF/dubbo/ 目錄:該目錄存放用戶自定義的 SPI 配置文件。
META-INF/dubbo/internal/ 目錄:該目錄存放 Dubbo 內(nèi)部使用的 SPI 配置文件。
4.使用
public class DubboSPIMain { public static void main(String[] args) { //default,會(huì)取@SPI注解里定義的key對(duì)應(yīng)的實(shí)現(xiàn) // DubboSPIService defaultExtensionService = ExtensionLoader.getExtensionLoader(DubboSPIService.class).getDefaultExtension(); // defaultExtensionService.sayHello(); DubboSPIService dubboSPIService = ExtensionLoader.getExtensionLoader(DubboSPIService.class).getExtension("xiaoming"); dubboSPIService.sayHello(); } }
結(jié)果:
源碼分析
ExtensionLoader.getExtensionLoader(DubboSPIService.class).getExtension("xiaoming");
dubbo SPI的核心就是ExtensionLoader類
1.ExtensionLoader#getExtensionLoader()
該方法主要是,從一個(gè)map里取key為當(dāng)前傳進(jìn)來的接口Class的value(value是ExtensionLoader對(duì)象),如果取不到,我們就往這個(gè)map里put一份這樣的key-value。value是new ExtensionLoader(type)傳進(jìn)去的type是接口的Class對(duì)象,最后會(huì)賦值給ExtensionLoader對(duì)象的type屬性,后面會(huì)用到
。
2.拿到ExtensionLoader對(duì)象后,通過ExtensionLoader#getExtension()獲取具體的實(shí)現(xiàn)的實(shí)例
首先會(huì)取緩存里拿,沒拿到就調(diào)用createExtension()方法取創(chuàng)建所需要的實(shí)例,最后塞入緩存。
3.createExtension方法
通過getExtension(“xiaoming”)傳進(jìn)來的name=xiaoming,從SPI配置文件獲取到所需要實(shí)現(xiàn)類的全類名,通過反射拿到實(shí)現(xiàn)類的Class對(duì)象,最后通過反射拿到相應(yīng)的實(shí)例。核心是getExtensionClasses()方法。
4.getExtensionClasses()
getExtensionClasses()方法返回一個(gè)Map,key為SPI配置文件中的key,value為SPI配置文件中,實(shí)現(xiàn)類的Class對(duì)象。
可以看到,代碼中用來大量的緩存機(jī)制,鎖的雙檢查。cacheDefaultExtensionName()方法里會(huì)拿到SPI注解上配置的默認(rèn)key,然后賦值給cachedDefaultName屬性,如果使用getDefaultExtension()時(shí)候會(huì)使用到strategies,其實(shí)是通過java得SPI拿到得一個(gè)數(shù)組
5.循環(huán)三個(gè)SPI文件得目錄,分別調(diào)用loadDirectory方法
fileName最后在三次循環(huán)里,會(huì)拼出三個(gè)路徑,META-INF/dubbo/com.example.demo.service.DubboSPIService,這一個(gè)才是正確得路徑,然后獲得配置文件得絕對(duì)路徑。然后會(huì)執(zhí)行l(wèi)oadResource()方法讀取SPI配置文件
- META-INF/dubbo/com.example.demo.service.DubboSPIService
- META-INF/services/com.example.demo.service.DubboSPIService
- META-INF/dubbo/internal/com.example.demo.service.DubboSPIService
6.loadResource()讀取SPI配置文件
一行一行讀配置文件里得key-value,然后通過Class.forName()獲取類得Class對(duì)象。然后put到第四步定義得空Map,extensionClasses這個(gè)Map里,再返回到第三步得getExtensionClasses()方法。
好了,今天先到這里,凌晨了。。。Adaptive 注解 - 自適應(yīng)擴(kuò)展下次有時(shí)間再寫。
到此這篇關(guān)于Java和Dubbo的SPI機(jī)制原理解析的文章就介紹到這了,更多相關(guān)Java和Dubbo的SPI內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java生產(chǎn)者和消費(fèi)者例子_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
生產(chǎn)者-消費(fèi)者(producer-consumer)問題,也稱作有界緩沖區(qū)(bounded-buffer)問題,兩個(gè)進(jìn)程共享一個(gè)公共的固定大小的緩沖區(qū)。下文通過實(shí)例給大家介紹java生產(chǎn)者和消費(fèi)者,感興趣的朋友一起學(xué)習(xí)吧2017-05-05Java 網(wǎng)絡(luò)爬蟲基礎(chǔ)知識(shí)入門解析
這篇文章主要介紹了Java 網(wǎng)絡(luò)爬蟲基礎(chǔ)知識(shí)入門解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10Jenkins Maven pom jar打包未拉取最新包解決辦法
包版本號(hào)未變更新后,jenkins打包不會(huì)拉取最新包,本文主要介紹了Jenkins Maven pom jar打包未拉取最新包解決辦法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02SpringBoot+阿里云OSS實(shí)現(xiàn)在線視頻播放的示例
這篇文章主要介紹了SpringBoot+阿里云OSS實(shí)現(xiàn)在線視頻播放的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11idea sql的xml文件出現(xiàn)紅色警告符的處理方式
這篇文章主要介紹了idea sql的xml文件出現(xiàn)紅色警告符處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04spring boot項(xiàng)目導(dǎo)入依賴后代碼報(bào)錯(cuò)問題的解決方法
這篇文章主要給大家介紹了關(guān)于spring boot項(xiàng)目導(dǎo)入依賴后代碼報(bào)錯(cuò)問題的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08