站在一個(gè)框架作者的角度來(lái)說(shuō),定義一個(gè)接口,自己默認(rèn)給出幾個(gè)接口的實(shí)現(xiàn)類,同時(shí) 允許框架的使用者也能夠自定義接口的實(shí)現(xiàn)?,F(xiàn)在一個(gè)簡(jiǎn)單的問題就是:如何優(yōu)雅的根據(jù)一個(gè)接口來(lái)獲取該接口的所有實(shí)現(xiàn)類呢?
JDK SPI 正是為了優(yōu)雅解決這個(gè)問題而生,SPI 全稱為 (Service Provider Interface),即服務(wù)提供商接口,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制。目前有不少框架用它來(lái)做服務(wù)的擴(kuò)展發(fā)現(xiàn),簡(jiǎn)單來(lái)說(shuō),它就是一種動(dòng)態(tài)替換服務(wù)實(shí)現(xiàn)者的機(jī)制。
所以,Dubbo如此被廣泛接納的其中的 一個(gè)重要原因就是基于SPI實(shí)現(xiàn)的強(qiáng)大靈活的擴(kuò)展機(jī)制,開發(fā)者可自定義插件嵌入Dubbo,實(shí)現(xiàn)靈活的業(yè)務(wù)需求。
JDK為SPI的實(shí)現(xiàn)提供了工具類,即java.util.ServiceLoader,ServiceLoader中定義的SPI規(guī)范沒有什么特別之處,只需要有一個(gè)提供者配置文件(provider-configuration file),該文件需要在resource目錄META-INF/services
下,文件名就是服務(wù)接口的全限定名。
public interface Cmand { public void execute();}public class ShutdownCommand implements Cmand { public void execute() { System.out.println("shutdown...."); }}public class StartCommand implements Cmand { public void execute() { System.out.println("start...."); }}public class SPIMain { public static void main(String[] args) { ServiceLoader<Cmand> loader = ServiceLoader.load(Cmand.class); System.out.println(loader); for (Cmand Cmand : loader) { Cmand.execute(); } }}
Dubbo對(duì)JDK SPI進(jìn)行了擴(kuò)展,對(duì)服務(wù)提供者配置文件中的內(nèi)容進(jìn)行了改造,由原來(lái)的提供者類的全限定名列表改成了KV形式的列表,這也導(dǎo)致了Dubbo中無(wú)法直接使用JDK ServiceLoader,所以,與之對(duì)應(yīng)的,在Dubbo中有ExtensionLoader。
ExtensionLoader是擴(kuò)展點(diǎn)載入器,用于載入Dubbo中的各種可配置組件,比如:負(fù)載均衡策略(LoadBalance)、攔截器(Filter)、集群方式(Cluster)等。
總之,Dubbo為了應(yīng)對(duì)各種場(chǎng)景,它的所有內(nèi)部組件都是通過這種SPI的方式來(lái)管理的,這也是為什么Dubbo需要將服務(wù)提供者配置文件設(shè)計(jì)成KV鍵值對(duì)形式,這個(gè)K就是我們?cè)贒ubbo配置文件或注解中用到的K,Dubbo直接通過服務(wù)接口(上面提到的ProxyFactory、LoadBalance、Protocol、Filter等)和配置的K從ExtensionLoader拿到服務(wù)提供的實(shí)現(xiàn)類。
SPI 全稱為 Service Provider Interface,是一種服務(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 機(jī)制在第三方框架中也有所應(yīng)用,比如 Dubbo 就是通過 SPI 機(jī)制加載所有的組件。不過,Dubbo 并未使用 Java 原生的 SPI 機(jī)制,而是對(duì)其進(jìn)行了增強(qiáng),使其能夠更好的滿足需求。在 Dubbo 中,SPI 是一個(gè)非常重要的模塊?;?SPI,我們可以很容易的對(duì) Dubbo 進(jìn)行拓展。如果大家想要學(xué)習(xí) Dubbo 的源碼,SPI 機(jī)制務(wù)必弄懂。
Dubbo對(duì)SPI的擴(kuò)展是 通過ExtensionLoader來(lái)實(shí)現(xiàn)的。
Dubbo通過SPI注解定義了可擴(kuò)展的接口,如LoadBalance、Filter、Transporter等。每個(gè)類型的擴(kuò)展對(duì)應(yīng)一個(gè)ExtensionLoader。SPI的value參數(shù)決定了默認(rèn)的擴(kuò)展實(shí)現(xiàn)。
查看ExtensionLoader的源碼,可以看到Dubbo對(duì)JDK SPI 做了三個(gè)方面的擴(kuò)展:
IOC依賴注入功能:Adaptive實(shí)現(xiàn),就是生成一個(gè)代理類,這樣就可以根據(jù)實(shí)際調(diào)用時(shí)的一些參數(shù)動(dòng)態(tài)決定要調(diào)用的類了。
現(xiàn)在實(shí)現(xiàn)者A1含有setB()方法,會(huì)自動(dòng)注入一個(gè)接口B的實(shí)現(xiàn)者,此時(shí)注入B1還是B2呢?都不是,而是注入一個(gè)動(dòng)態(tài)生成的接口B的實(shí)現(xiàn)者 B$Adpative,該實(shí)現(xiàn)者能夠根據(jù)參數(shù)的不同,自動(dòng)引用B1或者B2來(lái)完成相應(yīng)的功能;
采用裝飾器模式進(jìn)行功能增強(qiáng),自動(dòng)包裝實(shí)現(xiàn),這種實(shí)現(xiàn)的類一般是自動(dòng)激活的,常用于包裝類,比如:Protocol的兩個(gè)實(shí)現(xiàn)類:ProtocolFilterWrapper、ProtocolListenerWrapper。
還是第2個(gè)的例子,接口A的另一個(gè)實(shí)現(xiàn)者AWrapper1。大體內(nèi)容如下:
private A a;AWrapper1(A a){ this.a=a;}
因此,當(dāng)在獲取某一個(gè)接口A的實(shí)現(xiàn)者A1的時(shí)候,已經(jīng)自動(dòng)被AWrapper1包裝了。
通過ExtensionLoader加載一個(gè)實(shí)現(xiàn)類的流程,大致如下:
獲取ExtensionLoader
加載所有擴(kuò)展類的具體實(shí)現(xiàn)類
PS:本人解析源碼不喜歡貼一大段代碼,因?yàn)橛袝r(shí)候貼一大段代碼出來(lái),讀者未必能看懂代碼到底在做什么。
一般我會(huì)把主流程寫出來(lái),細(xì)節(jié)讀者可自己深究。
SPI示例及概念
Dubbo SPI原理流程
聯(lián)系客服