1.jdk SPI介紹
SPI 全稱(chēng)為 Service Provider Interface冯乘,是一種服務(wù)發(fā)現(xiàn)機(jī)制恕洲。SPI 的
這樣可以在
僵闯,我們可以很容易的通過(guò) SPI 機(jī)制為我們的程序提供拓展功能。SPI 機(jī)制在第三方框架中也有所應(yīng)用藤滥,比如 Dubbo 就是通過(guò) SPI 機(jī)制加載所有的組件鳖粟。不過(guò),Dubbo 并未使用 Java 原生的 SPI 機(jī)制拙绊,而是對(duì)其進(jìn)行了增強(qiáng)向图,使其能夠更好的滿(mǎn)足需求。在 Dubbo 中标沪,SPI 是一個(gè)非常重要的模塊榄攀。基于 SPI金句,我們可以很容易的對(duì) Dubbo 進(jìn)行拓展檩赢。如果大家想要學(xué)習(xí) Dubbo 的源碼,SPI 機(jī)制務(wù)必弄懂违寞。接下來(lái)贞瞒,我們先來(lái)了解一下 Java SPI 與 Dubbo SPI 的用法,然后再來(lái)分析 Dubbo SPI 的源碼趁曼。
以上來(lái)自dubbo官方文檔
我們系統(tǒng)里抽象的各個(gè)模塊军浆,往往有很多不同的實(shí)現(xiàn)方案,比如日志模塊的方案挡闰,xml解析模塊瘾敢、jdbc模塊的方案等。面向的對(duì)象的設(shè)計(jì)里尿这,我們一般推薦模塊之間基于接口編程簇抵,模塊之間不對(duì)實(shí)現(xiàn)類(lèi)進(jìn)行硬編碼。一旦代碼里涉及具體的實(shí)現(xiàn)類(lèi)射众,就違反了可拔插的原則碟摆,如果需要替換一種實(shí)現(xiàn),就需要修改代碼叨橱。為了實(shí)現(xiàn)在模塊裝配的時(shí)候能不在程序里動(dòng)態(tài)指明典蜕,這就需要一種服務(wù)發(fā)現(xiàn)機(jī)制断盛。 java spi就是提供這樣的一個(gè)機(jī)制:為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制。有點(diǎn)類(lèi)似IOC的思想愉舔,就是將裝配的控制權(quán)移到程序之外钢猛,在模塊化設(shè)計(jì)中這個(gè)機(jī)制尤其重要。
SPI的具體約定:服務(wù)的提供者轩缤,提供了服務(wù)接口的一種實(shí)現(xiàn)之后命迈,在jar包的META-INF/services/目錄里同時(shí)創(chuàng)建一個(gè)以服務(wù)接口命名的文件。該文件里就是實(shí)現(xiàn)該服務(wù)接口的具體實(shí)現(xiàn)類(lèi)火的。而當(dāng)外部程序裝配這個(gè)模塊的時(shí)候壶愤,就能通過(guò)該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類(lèi)名,并裝載實(shí)例化馏鹤,完成模塊的注入征椒。 基于這樣一個(gè)約定就能很好的找到服務(wù)接口的實(shí)現(xiàn)類(lèi),而不需要再代碼里制定湃累。jdk提供服務(wù)實(shí)現(xiàn)查找的一個(gè)工具類(lèi):java.util.ServiceLoader
-
common-logging
apache最早提供的日志的門(mén)面接口勃救。只有接口,沒(méi)有實(shí)現(xiàn)治力。具體方案由各提供商實(shí)現(xiàn)剪芥, 發(fā)現(xiàn)日志提供商是通過(guò)掃描 META-INF/services/org.apache.commons.logging.LogFactory配置文件,通過(guò)讀取該文件的內(nèi)容找到日志提工商實(shí)現(xiàn)類(lèi)琴许。只要我們的日志實(shí)現(xiàn)里包含了這個(gè)文件,并在文件里制定 LogFactory工廠(chǎng)接口的實(shí)現(xiàn)類(lèi)即可溉躲。
-
JDBC
jdbc4.0以前榜田, 開(kāi)發(fā)人員還需要基于Class.forName("xxx")的方式來(lái)裝載驅(qū)動(dòng),jdbc4也基于spi的機(jī)制來(lái)發(fā)現(xiàn)驅(qū)動(dòng)提供商了锻梳,可以通過(guò)META-INF/services/java.sql.Driver文件里指定實(shí)現(xiàn)類(lèi)的方式來(lái)暴露驅(qū)動(dòng)提供者.
jdbc連接過(guò)程 賈璉預(yù)執(zhí)事(加載驅(qū)動(dòng)箭券、鏈接數(shù)據(jù)庫(kù)、預(yù)執(zhí)行疑枯、執(zhí)行辩块、釋放資源)
//加載JDBC驅(qū)動(dòng)程序
Class.forName("com.mysql.jdbc.Driver") ;
//2、提供JDBC連接的URL
String url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
//3荆永、創(chuàng)建數(shù)據(jù)庫(kù)的連接
Connection con = DriverManager.getConnection(url , username , password ) ;
//4废亭、創(chuàng)建一個(gè)Statement
PreparedStatement pstmt = con.prepareStatement(sql) ;
//5、執(zhí)行SQL語(yǔ)句
ResultSet rs = stmt.executeQuery("SELECT * FROM ...") ;
while(rs.next()){
//do something
}
//6具钥、釋放資源
con.close()
我們都知道豆村,也聽(tīng)了無(wú)數(shù)遍,驅(qū)動(dòng)的加載是由Class.forName 方法完成的骂删。
由于JVM對(duì)類(lèi)的加載有一個(gè)邏輯是:在類(lèi)被需要的時(shí)候掌动,或者首次調(diào)用的時(shí)候就會(huì)把類(lèi)加載到JVM四啰。反過(guò)來(lái)也就是:如果類(lèi)沒(méi)有被需要的時(shí)候,一般是不會(huì)被加載到JVM的粗恢。
當(dāng)連接數(shù)據(jù)庫(kù)的時(shí)候我們調(diào)用了Class.forName語(yǔ)句之后柑晒,數(shù)據(jù)庫(kù)驅(qū)動(dòng)類(lèi)被加載到JVM,那么靜態(tài)初始化塊就會(huì)被執(zhí)行眷射,從而完成驅(qū)動(dòng)的注冊(cè)工作匙赞,也就是注冊(cè)到了JDBC的DriverManager類(lèi)中。
由于是靜態(tài)初始化塊中完成的加載凭迹,所以也就不必?fù)?dān)心驅(qū)動(dòng)被加載多次罚屋,原因可以參考單例模式相關(guān)的知識(shí)。
拋棄Class.forName()
在JDBC 4.0之后實(shí)際上我們不需要再調(diào)用Class.forName來(lái)加載驅(qū)動(dòng)程序了嗅绸,我們只需要把驅(qū)動(dòng)的jar包放到工程的類(lèi)加載路徑里脾猛,那么驅(qū)動(dòng)就會(huì)被自動(dòng)加載。
這個(gè)自動(dòng)加載采用的技術(shù)叫做SPI鱼鸠,數(shù)據(jù)庫(kù)驅(qū)動(dòng)廠(chǎng)商也都做了更新猛拴。可以看一下jar包里面的META-INF/services目錄蚀狰,里面有一個(gè)java.sql.Driver的文件愉昆,文件里面包含了驅(qū)動(dòng)的全路徑名。
比如mysql-connector里面的內(nèi)容:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
那么SPI技術(shù)又是在什么階段加載的數(shù)據(jù)庫(kù)驅(qū)動(dòng)呢麻蹋?看一下JDBC的DriverManager類(lèi)就知道了跛溉。
public class DriverManager {
static {
loadInitialDrivers();//......1
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);//.....2
Iterator driversIterator = loadedDrivers.iterator();
//.....
}
上述代碼片段標(biāo)記…1的位置是在DriverManager類(lèi)加載是執(zhí)行的靜態(tài)初始化塊,這里會(huì)調(diào)用loadInitialDrivers方法扮授。
再看loadInitialDrivers方法里面標(biāo)記…2的位置芳室,這里調(diào)用的 ServiceLoader.load(Driver.class); 就會(huì)加載所有在META-INF/services/java.sql.Driver文件里邊的類(lèi)到JVM內(nèi)存,完成驅(qū)動(dòng)的自動(dòng)加載刹勃。
這就是SPI的優(yōu)勢(shì)所在堪侯,能夠自動(dòng)的加載類(lèi)到JVM內(nèi)存。這個(gè)技術(shù)在阿里的dubbo框架里面也占到了很大的分量荔仁,有興趣的朋友可以看一下dubbo的代碼伍宦,或者百度一下dubbo的擴(kuò)展機(jī)制。
JDBC如何區(qū)分多個(gè)驅(qū)動(dòng)乏梁?
一個(gè)項(xiàng)目里邊很可能會(huì)即連接MySQL次洼,又連接Oracle,這樣在一個(gè)工程里邊就存在了多個(gè)驅(qū)動(dòng)類(lèi)遇骑,那么這些驅(qū)動(dòng)類(lèi)又是怎么區(qū)分的呢滓玖?
關(guān)鍵點(diǎn)就在于getConnection的步驟,DriverManager.getConnection中會(huì)遍歷所有已經(jīng)加載的驅(qū)動(dòng)實(shí)例去創(chuàng)建連接质蕉,當(dāng)一個(gè)驅(qū)動(dòng)創(chuàng)建連接成功時(shí)就會(huì)返回這個(gè)連接势篡,同時(shí)不再調(diào)用其他的驅(qū)動(dòng)實(shí)例翩肌。DriverManager關(guān)鍵代碼如下:
private static Connection getConnection(
//.....
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
是不是每個(gè)驅(qū)動(dòng)實(shí)例都真真實(shí)實(shí)的要嘗試建立連接呢?不是的禁悠!
比如碍侦,MySQL驅(qū)動(dòng)類(lèi)中的關(guān)鍵代碼:
public boolean acceptsURL(String url) throws SQLException {
return (parseURL(url, null) != null);
}
public Properties parseURL(String url, Properties defaults)
throws java.sql.SQLException {
Properties urlProps = (defaults != null) ? new Properties(defaults)
: new Properties();
if (url == null) {
return null;
}
if (!StringUtils.startsWithIgnoreCase(url, URL_PREFIX)
&& !StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX)
&& !StringUtils.startsWithIgnoreCase(url,
LOADBALANCE_URL_PREFIX)
&& !StringUtils.startsWithIgnoreCase(url,
REPLICATION_URL_PREFIX)) { //$NON-NLS-1$
return null;
}
//......
2.SPI示例
3.JDK SPI缺點(diǎn)
- 需要遍歷所有的實(shí)現(xiàn)粱坤,并實(shí)例化,然后我們?cè)谘h(huán)中才能找到我們需要的實(shí)現(xiàn)瓷产。
- 配置文件中只是簡(jiǎn)單的列出了所有的擴(kuò)展實(shí)現(xiàn)站玄,而沒(méi)有給他們命名。導(dǎo)致在程序中很難去準(zhǔn)確的引用它們濒旦。
- 擴(kuò)展如果依賴(lài)其他的擴(kuò)展株旷,做不到自動(dòng)注入和裝配
- 不提供類(lèi)似于Spring的AOP功能
- 擴(kuò)展很難和其他的框架集成,比如擴(kuò)展里面依賴(lài)了一個(gè)Spring bean尔邓,原生的Java SPI不支持
所以Java SPI應(yīng)付一些簡(jiǎn)單的場(chǎng)景是可以的晾剖,但對(duì)于Dubbo华临,它的功能還是比較弱的崖叫。Dubbo對(duì)原生SPI機(jī)制進(jìn)行了一些擴(kuò)展。接下來(lái)亮元,我們就更深入地了解下Dubbo的SPI機(jī)制灯节。
4.dubbo SPI
擴(kuò)展點(diǎn)(Extension Point)
是一個(gè)Java的接口循头。
擴(kuò)展(Extension)
擴(kuò)展點(diǎn)的實(shí)現(xiàn)類(lèi)。
擴(kuò)展實(shí)例
擴(kuò)展點(diǎn)實(shí)現(xiàn)類(lèi)的實(shí)例炎疆。
擴(kuò)展自適應(yīng)實(shí)例(Extension Adaptive Instance)
第一次接觸這個(gè)概念時(shí)卡骂,可能不太好理解(我第一次也是這樣的...)。如果稱(chēng)它為擴(kuò)展代理類(lèi)磷雇,可能更好理解些。擴(kuò)展的自適應(yīng)實(shí)例其實(shí)就是一個(gè)Extension的代理躏救,它實(shí)現(xiàn)了擴(kuò)展點(diǎn)接口唯笙。在調(diào)用擴(kuò)展點(diǎn)的接口方法時(shí),會(huì)根據(jù)實(shí)際的參數(shù)來(lái)決定要使用哪個(gè)擴(kuò)展盒使。比如一個(gè)IRepository的擴(kuò)展點(diǎn)崩掘,有一個(gè)save方法。有兩個(gè)實(shí)現(xiàn)MysqlRepository和MongoRepository少办。IRepository的自適應(yīng)實(shí)例在調(diào)用接口方法的時(shí)候苞慢,會(huì)根據(jù)save方法中的參數(shù),來(lái)決定要調(diào)用哪個(gè)IRepository的實(shí)現(xiàn)英妓。如果方法參數(shù)中有repository=mysql挽放,那么就調(diào)用MysqlRepository的save方法绍赛。如果repository=mongo,就調(diào)用MongoRepository的save方法辑畦。和面向?qū)ο蟮难舆t綁定很類(lèi)似吗蚌。為什么Dubbo會(huì)引入擴(kuò)展自適應(yīng)實(shí)例的概念呢?
Dubbo中的配置有兩種纯出,一種是固定的系統(tǒng)級(jí)別的配置蚯妇,在Dubbo啟動(dòng)之后就不會(huì)再改了。還有一種是運(yùn)行時(shí)的配置暂筝,可能對(duì)于每一次的RPC箩言,這些配置都不同。比如在xml文件中配置了超時(shí)時(shí)間是10秒鐘焕襟,這個(gè)配置在Dubbo啟動(dòng)之后陨收,就不會(huì)改變了。但針對(duì)某一次的RPC調(diào)用胧洒,可以設(shè)置它的超時(shí)時(shí)間是30秒鐘畏吓,以覆蓋系統(tǒng)級(jí)別的配置。對(duì)于Dubbo而言卫漫,每一次的RPC調(diào)用的參數(shù)都是未知的菲饼。只有在運(yùn)行時(shí),根據(jù)這些參數(shù)才能做出正確的決定列赎。
很多時(shí)候宏悦,我們的類(lèi)都是一個(gè)單例的,比如Spring的bean包吝,在Spring bean都實(shí)例化時(shí)饼煞,如果它依賴(lài)某個(gè)擴(kuò)展點(diǎn),但是在bean實(shí)例化時(shí)诗越,是不知道究竟該使用哪個(gè)具體的擴(kuò)展實(shí)現(xiàn)的砖瞧。這時(shí)候就需要一個(gè)代理模式了,它實(shí)現(xiàn)了擴(kuò)展點(diǎn)接口嚷狞,方法內(nèi)部可以根據(jù)運(yùn)行時(shí)參數(shù)块促,動(dòng)態(tài)的選擇合適的擴(kuò)展實(shí)現(xiàn)。而這個(gè)代理就是自適應(yīng)實(shí)例床未。 自適應(yīng)擴(kuò)展實(shí)例在Dubbo中的使用非常廣泛竭翠,Dubbo中,每一個(gè)擴(kuò)展都會(huì)有一個(gè)自適應(yīng)類(lèi)薇搁,如果我們沒(méi)有提供斋扰,Dubbo會(huì)使用字節(jié)碼工具為我們自動(dòng)生成一個(gè)。所以我們基本感覺(jué)不到自適應(yīng)類(lèi)的存在。后面會(huì)有例子說(shuō)明自適應(yīng)類(lèi)是怎么工作的传货。
@SPI
@SPI注解作用于擴(kuò)展點(diǎn)的接口上屎鳍,表明該接口是一個(gè)擴(kuò)展點(diǎn)∷鹄耄可以被Dubbo的ExtentionLoader加載哥艇。如果沒(méi)有此ExtensionLoader調(diào)用會(huì)異常。
@Adaptive
@Adaptive注解用在擴(kuò)展接口的方法上僻澎。表示該方法是一個(gè)自適應(yīng)方法貌踏。Dubbo在為擴(kuò)展點(diǎn)生成自適應(yīng)實(shí)例時(shí),如果方法有@Adaptive注解窟勃,會(huì)為該方法生成對(duì)應(yīng)的代碼祖乳。方法內(nèi)部會(huì)根據(jù)方法的參數(shù),來(lái)決定使用哪個(gè)擴(kuò)展秉氧。
ExtentionLoader
類(lèi)似于Java SPI的ServiceLoader眷昆,負(fù)責(zé)擴(kuò)展的加載和生命周期維護(hù)。
擴(kuò)展別名
和Java SPI不同汁咏,Dubbo中的擴(kuò)展都有一個(gè)別名亚斋,用于在應(yīng)用中引用它們。比如
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
其中的random攘滩,roundrobin就是對(duì)應(yīng)擴(kuò)展的別名帅刊。這樣我們?cè)谂渲梦募惺褂胷andom或roundrobin就可以了。
路徑
和Java SPI從/META-INF/services目錄加載擴(kuò)展配置類(lèi)似漂问,Dubbo也會(huì)從以下路徑去加載擴(kuò)展配置文件:
- META-INF/dubbo/internal
- META-INF/dubbo
- META-INF/services
5.Dubbo的LoadBalance擴(kuò)展點(diǎn)解讀
在了解了Dubbo的一些基本概念后赖瞒,讓我們一起來(lái)看一個(gè)Dubbo中實(shí)際的擴(kuò)展點(diǎn),對(duì)這些概念有一個(gè)更直觀(guān)的認(rèn)識(shí)蚤假。
我們選擇的是Dubbo中的擴(kuò)展點(diǎn)栏饮。Dubbo中的一個(gè)服務(wù),通常有多個(gè)Provider磷仰,consumer調(diào)用服務(wù)時(shí)袍嬉,需要在多個(gè)Provider中選擇一個(gè)。這就是一個(gè)LoadBalance灶平。我們一起來(lái)看看在Dubbo中伺通,LoadBalance是如何成為一個(gè)擴(kuò)展點(diǎn)的。
package com.alibaba.dubbo.rpc.cluster;
@SPI("random")
public interface LoadBalance {
@Adaptive({"loadbalance"})
<T> Invoker<T> select(List<Invoker<T>> var1, URL var2, Invocation var3) throws RpcException;
}
LoadBalance接口只有一個(gè)select方法民逼。select方法從多個(gè)invoker中選擇其中一個(gè)泵殴。上面代碼中和Dubbo SPI相關(guān)的元素有:
- @SPI("random") @SPI作用于LoadBalance接口涮帘,表示接口LoadBalance是一個(gè)擴(kuò)展點(diǎn)拼苍。如果沒(méi)有@SPI注解,試圖去加載擴(kuò)展時(shí),會(huì)拋出異常疮鲫。@SPI注解有一個(gè)參數(shù)吆你,該參數(shù)表示該擴(kuò)展點(diǎn)的默認(rèn)實(shí)現(xiàn)的別名。如果沒(méi)有顯示的指定擴(kuò)展俊犯,就使用默認(rèn)實(shí)現(xiàn)妇多。默認(rèn)實(shí)現(xiàn)是"random",是一個(gè)隨機(jī)負(fù)載均衡的實(shí)現(xiàn)燕侠。 random的定義在配置文件
META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.LoadBalance
中:
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
leastactive=com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
consistenthash=com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance
可以看到文件中定義了4個(gè)LoadBalance的擴(kuò)展實(shí)現(xiàn)者祖。由于負(fù)載均衡的實(shí)現(xiàn)不是本次的內(nèi)容,這里就不過(guò)多說(shuō)明绢彤。只用知道Dubbo提供了4種負(fù)載均衡的實(shí)現(xiàn)七问,我們可以通過(guò)xml文件,properties文件茫舶,JVM參數(shù)顯式的指定一個(gè)實(shí)現(xiàn)械巡。如果沒(méi)有,默認(rèn)使用隨機(jī)饶氏。
-
@Adaptive("loadbalance")
@Adaptive注解修飾select方法讥耗,表明方法select方法是一個(gè)可自適應(yīng)的方法。Dubbo會(huì)自動(dòng)生成該方法對(duì)應(yīng)的代碼疹启。當(dāng)調(diào)用select方法時(shí)古程,會(huì)根據(jù)具體的方法參數(shù)來(lái)決定調(diào)用哪個(gè)擴(kuò)展實(shí)現(xiàn)的select方法。@Adaptive注解的參數(shù)loadbalance表示方法參數(shù)中的loadbalance的值作為實(shí)際要調(diào)用的擴(kuò)展實(shí)例皮仁。 但奇怪的是籍琳,我們發(fā)現(xiàn)select的方法中并沒(méi)有l(wèi)oadbalance參數(shù),那怎么獲取loadbalance的值呢贷祈?select方法中還有一個(gè)URL類(lèi)型的參數(shù)趋急,Dubbo就是從URL中獲取loadbalance的值的。這里涉及到Dubbo的URL總線(xiàn)模式势誊,簡(jiǎn)單說(shuō)呜达,URL中包含了RPC調(diào)用中的所有參數(shù)。URL類(lèi)中有一個(gè)Map字段粟耻,parameters中就包含了loadbalance查近。
LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName);
使用ExtensionLoader.getExtensionLoader(LoadBalance.class)
方法獲取一個(gè)ExtensionLoader
的實(shí)例,然后調(diào)用getExtension
挤忙,傳入一個(gè)擴(kuò)展的別名來(lái)獲取對(duì)應(yīng)的擴(kuò)展實(shí)例霜威。
6.自定義一個(gè)LoadBalance擴(kuò)展
1.實(shí)現(xiàn)LoadBalance接口,首先,編寫(xiě)一個(gè)自己實(shí)現(xiàn)的LoadBalance册烈,因?yàn)槭菫榱搜菔綝ubbo的擴(kuò)展機(jī)制戈泼,而不是LoadBalance的實(shí)現(xiàn),所以這里L(fēng)oadBalance的實(shí)現(xiàn)非常簡(jiǎn)單,選擇第一個(gè)invoker大猛,并在控制臺(tái)輸出一條日志扭倾。
public class DemoLoadBalance implements LoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
System.out.println("DemoLoadBalance : Select the first invoker...");
return invokers.get(0);
}
}
2.META-INF/dubbo
下添加擴(kuò)展配置文件
demo=com.dubbo.spi.demo.consumer.DemoLoadBalance
- 通過(guò)上面的兩步,已經(jīng)添加了一個(gè)名字為demo的LoadBalance實(shí)現(xiàn)挽绩,并在配置文件中進(jìn)行了相應(yīng)的配置膛壹。接下來(lái),需要顯式的告訴Dubbo使用demo的負(fù)載均衡實(shí)現(xiàn)唉堪。如果是通過(guò)spring的方式使用Dubbo模聋,可以在xml文件中進(jìn)行設(shè)置。
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:reference id="helloService" interface="com.dubbo.spi.demo.api.IHelloService" loadbalance="demo" />
啟動(dòng)Dubbo
啟動(dòng)Dubbo唠亚,調(diào)用一次IHelloService撬槽,可以看到控制臺(tái)會(huì)輸出一條DemoLoadBalance: Select the first invoker...
日志。說(shuō)明Dubbo的確是使用了我們自定義的LoadBalance趾撵。
總結(jié)dubbo SPI
- 對(duì)Dubbo進(jìn)行擴(kuò)展侄柔,不需要改動(dòng)Dubbo的源碼
- 自定義的Dubbo的擴(kuò)展點(diǎn)實(shí)現(xiàn),是一個(gè)普通的Java類(lèi)占调,Dubbo沒(méi)有引入任何Dubbo特有的元素暂题,對(duì)代碼侵入性幾乎為零。
- 將擴(kuò)展注冊(cè)到Dubbo中究珊,只需要在ClassPath中添加配置文件薪者。使用簡(jiǎn)單。而且不會(huì)對(duì)現(xiàn)有代碼造成影響剿涮。符合開(kāi)閉原則言津。
- Dubbo的擴(kuò)展機(jī)制支持IoC,AoP等高級(jí)功能
- Dubbo的擴(kuò)展機(jī)制能很好的支持第三方IOC容器,默認(rèn)支持Spring Bean取试,可自己擴(kuò)展來(lái)支持其他容器悬槽,比如Google的Guice。
- 切換擴(kuò)展點(diǎn)的實(shí)現(xiàn)瞬浓,只需要在配置文件中修改具體的實(shí)現(xiàn)初婆,不需要改代碼。使用方便猿棉。