Java的SPI機制分析

一、問題引出:java

     JDK的標準SPI(Service Provider Interface) 被在較多的地方使用到,例如咱們經常使用的JDBC中:mysql

DriverManager.getConnection()這個方法:sql

@CallerSensitive
public static Connection getConnection(String url,
    java.util.Properties info) throws SQLException {
    return (getConnection(url, info, Reflection.getCallerClass()));
}

繼續點進去,會找到:apache

for(DriverInfo aDriver : registeredDrivers) {
    // If the caller does not have permission to load the driver then
    // skip it.
    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());
    }

}

其中核心代碼中有:Connection con = aDriver.driver.connect(url, info);編程

其中aDriver.driver是個接口,因此點進去的connect方法只是個Driver接口裏面定義的方法,再看實現類,發如今:ruby

(1)mysql/mysql-connector-java/5.1.36/mysql-connector-java-5.1.36.jar!/com/mysql/fabric/jdbc/FabricMySQLDriver.java中mybatis

(2)也在org/mybatis/mybatis/3.3.0/mybatis-3.3.0.jar!/org/apache/ibatis/datasource/unpooled/UnpooledDataSource.java中ide

這兩個並非jdbc的jar包!查資料能夠知道,對於JavaEE定義了JDBC的標準。具體實現是不一樣提供商提供的。因此java.sql.Driver接口是Java對外公開的一個加載驅動接口,Java並未實現,至於實現這個接口由各個Jdbc廠商去實現就好了,能夠解耦,使得更具備靈活性。工具

正是基於SPI,可讓系統找到具體的實現服務,經過查看具體實現jar包的META-INF/services與接口同名的文件中的內容:編碼

例如mysql 的實現包下:mysql/mysql-connector-java/5.1.36/mysql-connector-java-5.1.36.jar!/META-INF/services/java.sql.Driver的文件內容爲:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

二、Java SPI 思想分析:

(1)當咱們的系統裏面抽象的各個模塊,每每有不少不一樣的實現方案,好比日誌處理模塊、xml解析模塊、過濾器的模塊等,通常咱們模塊之間是基於接口編程的,模塊之間不會對具體實現類進行硬編碼。一旦代碼裏涉及到具體實現類時,就違反了可插拔的原則,若是須要替換一種實現,就須要修改代碼。爲了實如今模塊裝配的時候能不在程序裏動態指明,這就須要一種服務發現機制。Java SPI 就是提供了這樣一種機制:爲某個接口尋找服務實現的機制。

(2)spi規範約定:

當服務的提供者,提供了服務接口的一種實現以後,在jar包的META-INF/services/目錄裏同時建立一個以服務接口命名的文件。該文 件裏就是實現該服務接口的具體實現類。而當外部程序裝配這個

模塊的時候,就能經過該jar包META-INF/services/裏的配置文件找到具體的 實現類名,並裝載實例化,完成模塊的注入。 基於這樣一個約定就能很好的找到服務接口的實現類,而不須要再代碼裏制定。jdk提供服務實現查找的一個工具類:java.util.ServiceLoader

(3)使用場景:

a、jdbc

b、common-logging

c、Dubbo等

(4)簡單的例子:

package com.jason.spi;

public interface Search {
    String search(String keyword);
}
package com.jason.spi;

public class FileSearch implements Search {

    @Override
    public String search(String keyword) {
        System.out.println("now use file system search. keyword:" + keyword);
        return null;
    }
}
package com.jason.spi;

public class DatabaseSearch implements Search {
  
    @Override  
    public String search(String keyword) {
        System.out.println("now use database search. keyword:" + keyword);  
        return null;  
    }
}
package com.jason.spi;

import java.util.Iterator;
import java.util.ServiceLoader;

public class SearchTest {
  
    public static void main(String[] args) {  
        ServiceLoader<Search> s = ServiceLoader.load(Search.class);
        Iterator<Search> searchs = s.iterator();
        if (searchs.hasNext()) {
            System.out.println("test");
            Search search = searchs.next();
            search.search("test");
        }  
    }  
}

三、JDK標準的SPI有必定不足,Dubbo基於JDK的SPI機制作了必定的擴展:

(1)JDK標準的SPI會一次性實例化擴展點全部實現,若是有擴展實現初始化很耗時,但若是沒用上也加載,會很浪費資源。

(2)若是擴展點加載失敗,連擴展點的名稱都拿不到了。好比:JDK標準的ScriptEngine,經過getName();獲取腳本類型的名稱,但若是RubyScriptEngine由於所依賴的jruby.jar不存在,致使RubyScriptEngine類加載失敗,這個失敗緣由被吃掉了,和ruby對應不起來,當用戶執行ruby腳本時,會報不支持ruby,而不是真正失敗的緣由。

(3)增長了對擴展點IoC和AOP的支持,一個擴展點能夠直接setter注入其它擴展點。

Dubbo約定:
在擴展類的jar包內,放置擴展點配置文件:META-INF/dubbo/接口全限定名,內容爲:配置名=擴展實現類全限定名,多個實現類用換行符分隔。 
(注意:這裏的配置文件是放在你本身的jar包內,不是dubbo自己的jar包內,Dubbo會全ClassPath掃描全部jar包內同名的這個文件,而後進行合併)

擴展Dubbo的協議示例: 
在協議的實現jar包內放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,內容爲:

xxx=com.alibaba.xxx.XxxProtocol
相關文章
相關標籤/搜索