Dubbo是由阿里巴巴開源的一款高性能、輕量級的開源Java Rpc(遠程過程調用)框架,提供三大核心能力:面向接口的遠程方法調用、智能容錯和負載均衡、服務自動注冊與發現。
在Dubbo的源碼中,下面這種句式出現比較多,比如如下句式:通過ExtensionLoader獲取Protocol接口的代理類。
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
仔細翻看dubbo中的源碼,Protocol接口的實現類有很多種,那么在程序的執行中怎么得到對應的實現類,怎么去動態的擴展接口實現,這些問題就是今天討論的重點。
一、Spi是什么。
Spi全稱為(Service Provider Interface),是Jdk內置的一種服務提供發現機制,目前有不少框架用他來做服務的拓展發現,簡單說Spi是一種動態替換發現機制,使用Spi機制的優勢是實現解耦,使第三方服務模塊的裝配控制邏輯與調用者的業務代碼分離。
二、Java中JDK的Spi實現。
Java中如果想要使用SPI功能,先提供標准服務接口,然后在提供相關接口實現的調用者,這樣就可以通過spi機制中約定好的信息進行查詢相應的接口實現
SPI遵循以下約定
1)當服務提供者提供了一個服務(接口)的具體實現后,在classpath下的META-INF/services目錄下創建一個以“接口全限定名”命名的文件,內容為實現類的全限定名。
2)接口實現類所在的jar包放在駐車鞥徐的classpath中;
3)主程序通過java.util.ServiceLoader動態狀態實現模塊(類),它通過掃描META-INF/services目錄下的配置文件找到實現類的全限定名,把類加載到JVM。
4)Spi的實現類必須攜帶一個無參構造
JDK中的Spi機制應用比較廣泛比如說common-logging、JDBC等,這里使用JDBC進行舉例
1、JDBC接口定義
首先在java 中定義接口java.sql.Dirver並沒有具體的實現,具體的實現由不同的廠商來提供
2、mysql實現
在mysql的jar包mysql-connector-java-6.0.6.jar
中,可以找到META-INF/services
目錄,該目錄下會有一個名字為java.sql.Driver
的文件,文件內容是com.mysql.cj.jdbc.Driver
,這里面的內容就是針對Java中定義的接口的實現。
3、查找調用在DriverManager類的靜態代碼塊loadInitialDrivers();方法中
感興趣的小伙伴可以去翻看源碼哈~這里我就不展示源碼了,做一個簡單的模擬案例進行展示,項目目錄如下所示
源碼如下:
Driver類

package city.albert; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/8/25 5:09 PM */ public interface Driver { /** * 模擬獲取驅動名稱 * @return */ String driverName(); }
MysqlDriver實現類

package city.albert.impl; import city.albert.Driver; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/8/25 5:12 PM */ public class MysqlDriver implements Driver { @Override public String driverName() { return "mysql 驅動"; } }
OricalDriver實現類

package city.albert.impl; import city.albert.Driver; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/8/25 5:14 PM */ public class OricalDriver implements Driver{ @Override public String driverName() { return "orical 驅動"; } }
項目的classpath下META-INF/services
目錄下創建名為“city.albert.Driver”的文件夾指定你的實現類即可,我這里兩個類同時指定內容為:
city.albert.impl.MysqlDriver
city.albert.impl.OricalDriver
測試類如下
package city.albert.impl; import city.albert.Driver; import java.util.ServiceLoader; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/8/25 5:17 PM */ public class TestMain { public static void main(String[] args) { ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class); for (Driver driver : drivers) { System.out.println(driver.driverName()); } } }
結果兩個實現類都被加載
三、Dubbo中的Spi實現。
區別:在Dubbo的框架中並沒有使用JDK的Spi來實現,而是重寫了Spi的實現,Dubbo中的Spi形式上都是從Jar中加載對應的拓展類,但是Dubbo支持更多的加載路徑,也不是通過Iterator的形式調用而是通過名稱來定位具體的Provider,按照需要進行加載,並非Jdk中的一次性全部加載,效率更高,同時支持Provider以類似IOC的形式提供。
Dubbo自己實現Spi的目的 1、JDK標准的SPI會一次性實例化拓展點的所有實現,如果所有的實現初始化很耗時,並沒加載上也沒有用,就會很浪費資源。 2、如果有的拓展點加載失敗,則所有的拓展點無法使用。 3、提供了對拓展點包裝的功能(Adaptive),並且還支持Set的方式對其他拓展點進行注入
Dubbo中實現Spi與JDK形式上的區別
1、接口上需要添加“org.apache.dubbo.common.extension”包下的@SPI注解,在@SPI("spiService")注解中可以指定默認拓展點。
2、在META-INF/dubbo目錄下創建全限定名文件
3、全限定名文件中的內容是KEY-VALUE形式,value是實現類的全限定類名
4、調用者使用ExtensionLoader獲取加載
具體實現代碼
api項目中需要引入bubbo依賴因為使用到@Spi注解,版本號根據自己的選型來定哈~
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.5</version> </dependency>
api代碼

package city.albert; import org.apache.dubbo.common.extension.SPI; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/8/25 5:49 PM */ @SPI public interface DubboSpiService { String getName(); }
實現類

package city.albert.impl; import city.albert.DubboSpiService; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/8/25 5:51 PM */ public class DubboSpiServiceImpl implements DubboSpiService{ @Override public String getName() { return "dubbo——實現類——getName"; } }
META-INF/dubbo/city.albert.DubboSpiService文件內容
spiService = city.albert.impl.DubboSpiServiceImpl
調用類main
package city.albert; import org.apache.dubbo.common.extension.ExtensionLoader; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/8/25 5:53 PM */ public class DubboMain { public static void main(String[] args) { ExtensionLoader<DubboSpiService> loader = ExtensionLoader.getExtensionLoader(DubboSpiService.class); DubboSpiService service = loader.getExtension("spiService"); System.out.println(service.getName()); } }