版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
JDK/Dubbo/Spring三種SPI機(jī)制,誰(shuí)更好?SPI全稱為ServiceProviderInterface,是一種服務(wù)發(fā)現(xiàn)機(jī)制。SPI的本質(zhì)是將接口實(shí)現(xiàn)類的全限定名配置在文件中,并由服務(wù)加載器讀取配置文件,加載實(shí)現(xiàn)類。這樣可以在運(yùn)行時(shí),動(dòng)態(tài)為接口替換實(shí)現(xiàn)類。正因此特性,我們可以很容易的通過SPI機(jī)制為我們的程序提供拓展功能。SPI有什么用?舉個(gè)栗子,現(xiàn)在我們?cè)O(shè)計(jì)了一款全新的日志框架:super-logger。默認(rèn)以XML文件作為我們這款日志的配置文件,并設(shè)計(jì)了一個(gè)配置文件解析的接口:package
com.github.kongwu.spisamples;
public
interface
SuperLoggerConfiguration
{
void
configure(String
configFile);
}然后來一個(gè)默認(rèn)的XML實(shí)現(xiàn):package
com.github.kongwu.spisamples;
public
class
XMLConfiguration
implements
SuperLoggerConfiguration{
public
void
configure(String
configFile){
......
}
}那么我們?cè)诔跏蓟馕雠渲脮r(shí),只需要調(diào)用這個(gè)XMLConfiguration來解析XML配置文件即可。package
com.github.kongwu.spisamples;
public
class
LoggerFactory
{
static
{
SuperLoggerConfiguration
configuration
=
new
XMLConfiguration();
configuration.configure(configFile);
}
public
static
getLogger(Class
clazz){
......
}
}這樣就完成了一個(gè)基礎(chǔ)的模型,看起來也沒什么問題。不過擴(kuò)展性不太好,因?yàn)槿绻攵ㄖ?擴(kuò)展/重寫解析功能的話,我還得重新定義入口的代碼,LoggerFactory也得重寫,不夠靈活,侵入性太強(qiáng)了。比如現(xiàn)在用戶/使用方想增加一個(gè)yml文件的方式,作為日志配置文件,那么只需要新建一個(gè)YAMLConfiguration,實(shí)現(xiàn)SuperLoggerConfiguration就可以。但是……怎么注入呢,怎么讓LoggerFactory中使用新建的這個(gè)YAMLConfiguration?難不成連LoggerFactory也重寫了?如果借助SPI機(jī)制的話,這個(gè)事情就很簡(jiǎn)單了,可以很方便的完成這個(gè)入口的擴(kuò)展功能。下面就先來看看,利用JDK的SPI機(jī)制怎么解決上面的擴(kuò)展性問題。JDKSPIJDK中提供了一個(gè)SPI的功能,核心類是java.util.ServiceLoader。其作用就是,可以通過類名獲取在"META-INF/services/"下的多個(gè)配置實(shí)現(xiàn)文件。為了解決上面的擴(kuò)展問題,現(xiàn)在我們?cè)贛ETA-INF/services/下創(chuàng)建一個(gè)com.github.kongwu.spisamples.SuperLoggerConfiguration文件(沒有后綴)。文件中只有一行代碼,那就是我們默認(rèn)的com.github.kongwu.spisamples.XMLConfiguration(注意,一個(gè)文件里也可以寫多個(gè)實(shí)現(xiàn),回車分隔)META-INF/services/com.github.kongwu.spisamples.SuperLoggerConfiguration:
com.github.kongwu.spisamples.XMLConfiguration然后通過ServiceLoader獲取我們的SPI機(jī)制配置的實(shí)現(xiàn)類:ServiceLoader
serviceLoader
=
ServiceLoader.load(SuperLoggerConfiguration.class);
Iterator
iterator
=
serviceLoader.iterator();
SuperLoggerConfiguration
configuration;
while(iterator.hasNext())
{
//加載并初始化實(shí)現(xiàn)類
configuration
=
iterator.next();
}
//對(duì)最后一個(gè)configuration類調(diào)用configure方法
configuration.configure(configFile);最后在調(diào)整LoggerFactory中初始化配置的方式為現(xiàn)在的SPI方式:package
com.github.kongwu.spisamples;
public
class
LoggerFactory
{
static
{
ServiceLoader
serviceLoader
=
ServiceLoader.load(SuperLoggerConfiguration.class);
Iterator
iterator
=
serviceLoader.iterator();
SuperLoggerConfiguration
configuration;
while(iterator.hasNext())
{
configuration
=
iterator.next();//加載并初始化實(shí)現(xiàn)類
}
configuration.configure(configFile);
}
public
static
getLogger(Class
clazz){
......
}
}等等,這里為什么是用iterator?而不是get之類的只獲取一個(gè)實(shí)例的方法?試想一下,如果是一個(gè)固定的get方法,那么get到的是一個(gè)固定的實(shí)例,SPI還有什么意義呢?SPI的目的,就是增強(qiáng)擴(kuò)展性。將固定的配置提取出來,通過SPI機(jī)制來配置。那既然如此,一般都會(huì)有一個(gè)默認(rèn)的配置,然后通過SPI的文件配置不同的實(shí)現(xiàn),這樣就會(huì)存在一個(gè)接口多個(gè)實(shí)現(xiàn)的問題。要是找到多個(gè)實(shí)現(xiàn)的話,用哪個(gè)實(shí)現(xiàn)作為最后的實(shí)例呢?所以這里使用iterator來獲取所有的實(shí)現(xiàn)類配置。剛才已經(jīng)在我們這個(gè)
super-logger
包里增加了默認(rèn)的SuperLoggerConfiguration實(shí)現(xiàn)。為了支持YAML配置,現(xiàn)在在使用方/用戶的代碼里,增加一個(gè)YAMLConfiguration的SPI配置:META-INF/services/com.github.kongwu.spisamples.SuperLoggerConfiguration:
com.github.kongwu.spisamples.ext.YAMLConfiguration此時(shí)通過iterator方法,就會(huì)獲取到默認(rèn)的XMLConfiguration和我們擴(kuò)展的這個(gè)YAMLConfiguration兩個(gè)配置實(shí)現(xiàn)類了。在上面那段加載的代碼里,我們遍歷iterator,遍歷到最后,我們**使用最后一個(gè)實(shí)現(xiàn)配置作為最終的實(shí)例。再等等?最后一個(gè)?怎么算最后一個(gè)?使用方/用戶自定義的的這個(gè)YAMLConfiguration一定是最后一個(gè)嗎?這個(gè)真的不一定,取決于我們運(yùn)行時(shí)的ClassPath配置,在前面加載的jar自然在前,最后的jar里的自然當(dāng)然也在后面。所以如果用戶的包在ClassPath中的順序比super-logger的包更靠后,才會(huì)處于最后一個(gè)位置;如果用戶的包位置在前,那么所謂的最后一個(gè)仍然是默認(rèn)的XMLConfiguration。舉個(gè)栗子,如果我們程序的啟動(dòng)腳本為:java
-cp
super-logger.jar:a.jar:b.jar:main.jar
example.Main默認(rèn)的XMLConfigurationSPI配置在super-logger.jar,擴(kuò)展的YAMLConfigurationSPI配置文件在main.jar,那么iterator獲取的最后一個(gè)元素一定為YAMLConfiguration。搜索公眾號(hào)后端架構(gòu)師后臺(tái)回復(fù)“面試”,獲取一份驚喜禮包。但這個(gè)classpath順序如果反了呢?main.jar在前,super-logger.jar在后java
-cp
main.jar:super-logger.jar:a.jar:b.jar
example.Main這樣一來,iterator獲取的最后一個(gè)元素又變成了默認(rèn)的XMLConfiguration,我們使用JDKSPI沒啥意義了,獲取的又是第一個(gè),還是默認(rèn)的XMLConfiguration。由于這個(gè)加載順序(classpath)是由用戶指定的,所以無論我們加載第一個(gè)還是最后一個(gè),都有可能會(huì)導(dǎo)致加載不到用戶自定義的那個(gè)配置。所以這也是JDKSPI機(jī)制的一個(gè)劣勢(shì),無法確認(rèn)具體加載哪一個(gè)實(shí)現(xiàn),也無法加載某個(gè)指定的實(shí)現(xiàn),僅靠ClassPath的順序是一個(gè)非常不嚴(yán)謹(jǐn)?shù)姆绞紻ubboSPIDubbo就是通過SPI機(jī)制加載所有的組件。不過,Dubbo并未使用Java原生的SPI機(jī)制,而是對(duì)其進(jìn)行了增強(qiáng),使其能夠更好的滿足需求。在Dubbo中,SPI是一個(gè)非常重要的模塊?;赟PI,我們可以很容易的對(duì)Dubbo進(jìn)行拓展。如果大家想要學(xué)習(xí)Dubbo的源碼,SPI機(jī)制務(wù)必弄懂。接下來,我們先來了解一下JavaSPI與DubboSPI的用法,然后再來分析DubboSPI的源碼。Dubbo中實(shí)現(xiàn)了一套新的SPI機(jī)制,功能更強(qiáng)大,也更復(fù)雜一些。相關(guān)邏輯被封裝在了ExtensionLoader類中,通過ExtensionLoader,我們可以加載指定的實(shí)現(xiàn)類。DubboSPI所需的配置文件需放置在META-INF/dubbo路徑下,配置內(nèi)容如下(以下demo來自dubbo官方文檔)。optimusPrime
=
org.apache.spi.OptimusPrime
bumblebee
=
org.apache.spi.Bumblebee與JavaSPI實(shí)現(xiàn)類配置不同,DubboSPI是通過鍵值對(duì)的方式進(jìn)行配置,這樣我們可以按需加載指定的實(shí)現(xiàn)類。另外在使用時(shí)還需要在接口上標(biāo)注@SPI注解。下面來演示DubboSPI的用法:@SPI
public
interface
Robot
{
void
sayHello();
}
public
class
OptimusPrime
implements
Robot
{
@Override
public
void
sayHello()
{
System.out.println("Hello,
I
am
Optimus
Prime.");
}
}
public
class
Bumblebee
implements
Robot
{
@Override
public
void
sayHello()
{
System.out.println("Hello,
I
am
Bumblebee.");
}
}
public
class
DubboSPITest
{
@Test
public
void
sayHello()
throws
Exception
{
ExtensionLoader
extensionLoader
=
ExtensionLoader.getExtensionLoader(Robot.class);
Robot
optimusPrime
=
extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot
bumblebee
=
extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
}DubboSPI和JDKSPI最大的區(qū)別就在于支持“別名”,可以通過某個(gè)擴(kuò)展點(diǎn)的別名來獲取固定的擴(kuò)展點(diǎn)。就像上面的例子中,我可以獲取Robot多個(gè)SPI實(shí)現(xiàn)中別名為“optimusPrime”的實(shí)現(xiàn),也可以獲取別名為“bumblebee”的實(shí)現(xiàn),這個(gè)功能非常有用!通過@SPI注解的value屬性,還可以默認(rèn)一個(gè)“別名”的實(shí)現(xiàn)。比如在Dubbo中,默認(rèn)的是Dubbo私有協(xié)議:dubboprotocol-dubbo://**來看看Dubbo中協(xié)議的接口:@SPI("dubbo")
public
interface
Protocol
{
......
}在Protocol接口上,增加了一個(gè)@SPI注解,而注解的value值為Dubbo,通過SPI獲取實(shí)現(xiàn)時(shí)就會(huì)獲取
ProtocolSPI配置中別名為dubbo的那個(gè)實(shí)現(xiàn),com.alibaba.dubbo.rpc.Protocol文件如下:filter=tocol.ProtocolFilterWrapper
listener=tocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
dubbo=tocol.dubbo.DubboProtocol
injvm=tocol.injvm.InjvmProtocol
rmi=tocol.rmi.RmiProtocol
hessian=tocol.hessian.HessianProtocol
tocol.http.HttpProtocol
tocol.webservice.WebServiceProtocol
thrift=tocol.thrift.ThriftProtocol
memcached=tocol.memcached.MemcachedProtocol
redis=tocol.redis.RedisProtocol
rest=tocol.rest.RestProtocol
registry=egration.RegistryProtocol
qos=tocol.QosProtocolWrapper然后只需要通過getDefaultExtension,就可以獲取到@SPI注解上value對(duì)應(yīng)的那個(gè)擴(kuò)展實(shí)現(xiàn)了Protocol
protocol
=
ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension();
//protocol:
DubboProtocol還有一個(gè)Adaptive的機(jī)制,雖然非常靈活,但……用法并不是很“優(yōu)雅”,這里就不介紹了Dubbo的SPI中還有一個(gè)“加載優(yōu)先級(jí)”,優(yōu)先加載內(nèi)置(internal)的,然后加載外部的(external),按優(yōu)先級(jí)順序加載,如果遇到重復(fù)就跳過不會(huì)加載了。所以如果想靠classpath加載順序去覆蓋內(nèi)置的擴(kuò)展,也是個(gè)不太理智的做法,原因同上-加載順序不嚴(yán)謹(jǐn)SpringSPISpring的SPI配置文件是一個(gè)固定的文件-
META-INF/spring.factories,功能上和JDK的類似,每個(gè)接口可以有多個(gè)擴(kuò)展實(shí)現(xiàn),使用起來非常簡(jiǎn)單://獲取所有factories文件中配置的LoggingSystemFactory
List>
factories
=
SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class,
classLoader);下面是一段SpringBoot中spring.factories的配置#LoggingSystems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory
#PropertySourceLoaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
#ConfigDataLocationResolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.StandardConfigDataLocationResolver
......SpringSPI中,將所有的配置放到一個(gè)固定的文件中,省去了配置一大堆文件的麻煩。至于多個(gè)接口的擴(kuò)展配置,是用一個(gè)文件好,還是每個(gè)單獨(dú)一個(gè)文件好這個(gè),這個(gè)問題就見仁見智了(個(gè)人喜歡Spring這種,干凈利落)。搜索公眾號(hào)后端架構(gòu)師后臺(tái)回復(fù)“架構(gòu)整潔”,獲取一份驚喜禮包。Spring的SPI雖然屬于spring-framework(core),但是目前主要用在springboot中……和前面兩種SPI機(jī)制一樣,Spring也是支持ClassPath中存在多個(gè)spring.factories文件的,加載時(shí)會(huì)按照classpath的順序依次加載這些spring.factories文件,添加到一個(gè)ArrayList中。由于沒有別名,所以也沒有去重的概念,有多少就添加多少。但由于Spri
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024年貨物運(yùn)輸合同規(guī)定運(yùn)輸方式與責(zé)任
- 2025年度歷史建筑保護(hù)拆墻工程合作協(xié)議4篇
- 2024豬場(chǎng)租賃承包合同
- 2024節(jié)能減排協(xié)議書
- 《中樞性高熱患者的護(hù)理與治療》課件
- 2025年度新媒體運(yùn)營(yíng)與公關(guān)合作服務(wù)合同范本4篇
- 2024年05月云南廣發(fā)銀行昆明分行招考筆試歷年參考題庫(kù)附帶答案詳解
- 2025年度大數(shù)據(jù)分析服務(wù)合同樣本8篇
- 2025變頻器代理商銷售合同:市場(chǎng)拓展與品牌推廣合作3篇
- 二零二五年度高端酒店集團(tuán)食材供應(yīng)與服務(wù)合同3篇
- 常見老年慢性病防治與護(hù)理課件整理
- 履約情況證明(共6篇)
- 云南省迪慶藏族自治州各縣區(qū)鄉(xiāng)鎮(zhèn)行政村村莊村名居民村民委員會(huì)明細(xì)
- 設(shè)備機(jī)房出入登記表
- 六年級(jí)語(yǔ)文-文言文閱讀訓(xùn)練題50篇-含答案
- 醫(yī)用冰箱溫度登記表
- 零售學(xué)(第二版)第01章零售導(dǎo)論
- 大學(xué)植物生理學(xué)經(jīng)典05植物光合作用
- 口袋妖怪白金光圖文攻略2周目
- 光伏發(fā)電站集中監(jiān)控系統(tǒng)通信及數(shù)據(jù)標(biāo)準(zhǔn)
- 三年級(jí)下冊(cè)生字組詞(帶拼音)
評(píng)論
0/150
提交評(píng)論