Dubbo源碼分析-微內核插件式開發(SPI介紹)

一、前言

閱讀優秀的開源框架是程序員進步的一種捷徑。但願本身能堅持下去。有個好頭也有個好的結尾。這部分先講講dubbo最核心的設計思想。微內核插件式開發。這種思想貫穿了他的整個項目。在講微內核以前,可能先要講講java的SPI。其實dubbo就是在SPI設計思想基礎上進行了升級。java

二、什麼是SPI

SPI全稱Service Provider Interface。字面上理解是面向接口提供服務。他很好的詮釋了面向接口編程,以及OCP原則等。你們都知道,咱們在模塊化編程的時候,每每模塊之間是基於接口編程的。例如A模塊須要使用一種服務,但具體實現交由第三方實現。那麼A會定義出一個接口讓B去實現,可是A在實例化這個接口的實現時,每每須要硬編碼B的一種實現。這就會致使B若是換了實現方式,又或者A想使用C的實現方式,那麼須要修改A的實例化代碼去支持實現的修改切換。在OCP原則裏,這種修改是須要避免的。因此咱們須要一種實現發現機制。在實例化時不該該硬編碼一種實現,而是爲接口提供一種實現發現機制,而後把實例化這部分功能移到A模塊以外。這種思想跟IOC很像。咱們在用Spring的時候,就是把對象的裝配交給了Spring處理,這樣層於層之間才正真達到了面向接口編程。程序員

三、SPI實現思想

假如A模塊設計定義了一個接口。當B實現了這個接口以後,須要在B的jar包的META-INF/services/目錄裏添加一個文件,這個文件名字就是A定義的這個接口的全路徑名,內容就是B實現接口的類的全路徑名。此時,若是A須要使用B的實現,引入B的jar包。而後A可使用java.util.ServiceLoader去發現這個實現,並實例化後返回給A。這樣A在使用這個實現時,只是用定義好的接口引用了該實現,不用去硬編碼這個實現。若是有一天須要替換B的實現,咱們只要引入別的第三方jar,根本不用修改A的代碼。sql

四、看例子說話

例如A模塊定義了一個接口,以下:apache

public interface DemoApi {
    String sayHello(String name);
}

而後B實現這個接口,以下:編程

public class DemoApiImpl1 implements DemoApi {
    public String sayHello(String name) {
        return name + "你好,我是DemoApiImpl1實現";
    }
}

假如咱們不用SPI設計思想去設計程序,那麼我在A模塊中要須要下面這樣去使用DemoApi接口,這個問題就來了,我在設計A模塊的時候,須要去依賴B模塊,並且哪天我想替換B模塊的實現,我還得動A模塊的代碼,從新new一個別的實現。其實A模塊只須要使用DemoApi接口的服務,至於這個接口的具體實現根本不須要關心,更不須要事先依賴某種實現。框架

public static void main(String args[]) {
    DemoApi demoApi = new DemoApiImpl1();
    demoApi.sayHello("xuanner");
}

那麼,咱們來改進一下,使用SPI來編碼使用DemoApi接口的服務,以下:ide

public static void main(String args[]) {
    ServiceLoader<DemoApi> serviceLoader = ServiceLoader.load(DemoApi.class);
    Iterator<DemoApi> iterator = serviceLoader.iterator();
    System.out.println("能夠遍歷多種可能存在的實現");
    while (iterator.hasNext()) {
        DemoApi demoApi = iterator.next();
        demoApi.sayHello("xuanner")
    }
}

固然,B模塊在實現以後須要在他的JAR包的META-INF/services/目錄下放一個配置文件,看以下圖:模塊化

裏面的內容是實現類的全路徑,以下:編碼

com.xuan.spidemo.impl1.DemoApiImpl1

當A模塊須要B的實現的時候,只要進入B的JAR,ServiceLoader就會在JAR下找到對應接口的實現,而後實例化返回提供給A使用,若是有一天咱們須要替換用C去實現,那麼只要C實現代碼後,一樣在他的JAR下放上面的配置文件便可。spa

五、舉例現有的優秀實現

其中java.sql.Driver的實現就就是基於使用了SPI。還有commons-logging日誌系統。也是有基於SPI實現的,我只簡單的摘錄了幾個相關的片斷,只不過他好像不是使用了ServiceLoader類去尋找實現,而是本身實現加載文件的。

protected static final String SERVICE_ID = 
    "META-INF/services/org.apache.commons.logging.LogFactory";
if (factory == null) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID +
                              "] to define the LogFactory subclass to use...");
            }
            try {
                final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);

                if( is != null ) {
                    // This code is needed by EBCDIC and other strange systems.
                    // It's a fix for bugs reported in xerces
                    BufferedReader rd;
                    try {
                        rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                    } catch (java.io.UnsupportedEncodingException e) {
                        rd = new BufferedReader(new InputStreamReader(is));
                    }

六、簡單總結

其實SPI設計思想也很簡單,接地氣一點的解釋就是,我設計了一個接口,而後本身不實現,當須要使用這個接口的實現時,就用ServiceLoader類去各個依賴的第三方包中掃描,只要掃描到有實現的類,就進行實例化提供服務。對我來講,實現是徹底透明的,我只根據接口的方法來編程,正真作到了面向接口編程。因此當咱們須要換一種實現時,只要替換一下第三方依賴JAR就好了,個人其餘代碼就不用動了。這種方式用來給第三方本身擴展是否是很贊。對的。

相關文章
相關標籤/搜索