JavaSPI機制學習筆記


 

  最近在閱讀框架源代碼時,常常看到 SPI 的子包, 忍不住查了下: Service Provider Interface : 服務提供接口。

  JavaSPI 實際上是“基於接口的編程+策略模式+配置文件”組合實現的動態加載機制。具體而言:

       STEP1. 定義一組接口, 假設是 autocomplete.PrefixMatcher;

       STEP2. 寫出接口的一個或多個實現(autocomplete.EffectiveWordMatcher, autocomplete.SimpleWordMatcher);

       STEP3. 在 src/main/resources/ 下建立 /META-INF/services 目錄, 新增一個以接口命名的文件 autocomplete.PrefixMatcher, 內容是要應用的實現類(autocomplete.EffectiveWordMatcher 或 autocomplete.SimpleWordMatcher 或兩者);

       STEP4. 使用 ServiceLoader 來加載配置文件中指定的實現。 

  SPI 的應用之一是可替換的插件機制。比如查看 JDBC 數據庫驅動包,mysql-connector-java-5.1.18.jar 就有一個 /META-INF/services/java.sql.Driver 里面內容是 com.mysql.jdbc.Driver 。

  

 

   代碼示例:

    1. 編寫接口和實現類: autocomplete.PrefixMatcher,  autocomplete.EffectiveWordMatcher, autocomplete.SimpleWordMatcher 見 《輸入自動提示與補全功能的設計與實現》

  2. 在 src/main/resources/ 下建立文件 /META-INF/services/ autocomplete.PrefixMatcher 填入上述兩個類之一或兩者都填; 

  3. 編寫測試類。      

package autocomplete;

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

/**
 * Created by lovesqcc on 16-2-29.
 */
public class PrefixMatcherTest {

    public static void main(String[] args) {
        ServiceLoader<PrefixMatcher> matcher = ServiceLoader.load(PrefixMatcher.class);
        Iterator<PrefixMatcher> matcherIter = matcher.iterator();
        while (matcherIter.hasNext()) {
            PrefixMatcher wordMatcher = matcherIter.next();
            System.out.println(wordMatcher.getClass().getName());
            String[] prefixes = new String[] {"a", "b", "c", "d", "e", "f", "g", "i",
                    "l", "n", "p", "r", "s", "t", "v", "w", "do", "finally"};
            for (String prefix: prefixes) {
                System.out.println(wordMatcher.obtainMatchedWords(prefix));
            }
        }

    }
}

 

      要寫個 ServiceLoader 的簡單實現也不難: 1. 讀取配置文件,獲取實現類的全名稱字符串; 2. 使用 Java 反射機制來構造服務實現類的實例。可以使用泛型方法,避免獲取的時候做類型轉換。不過 JDK 自帶的 java.util.ServiceLoader 實現得更加嚴謹一些,使用了 ClassLoader 來加載類,並使用迭代器來獲取服務實現類。思路大體相同。

package autocomplete;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by lovesqcc on 16-2-29.
 * A very Simple JavaSPI implementation using java reflection
 */
public class SimpleServiceLoader {

    private static final String PREFIX = "/META-INF/services/";

    public static <T> List<T> load(Class<T> cls) {
        List<String> implClasses = readServiceFile(cls);
        List<T> implList = new ArrayList<T>();
        for (String implClass : implClasses) {
            Class<T> c = null;
            try {
                c = (Class<T>) Class.forName(implClass);
                implList.add(c.newInstance());
            } catch (Exception e) {
                return new ArrayList<T>();
            }
        }
        return implList;
    }

    private static List<String> readServiceFile(Class<?> cls) {
        String infName = cls.getCanonicalName();
        String fileName = cls.getResource(PREFIX+infName).getPath();
        try {
            BufferedReader br = new BufferedReader(new FileReader(new File(fileName)));
            String line = "";
            List<String> implClasses = new ArrayList<String>();
            while ((line = br.readLine()) != null) {
                implClasses.add(line);
            }
            return implClasses;
        } catch (FileNotFoundException fnfe) {
            System.out.println("File not found: " + fileName);
            return new ArrayList<String>();
        } catch (IOException ioe) {
            System.out.println("Read file failed: " + fileName);
            return new ArrayList<String>();
        }
    }

    public static void main(String[] args) {
        List<PrefixMatcher> implList = load(PrefixMatcher.class);
        if (implList != null && implList.size() >0) {
            for (PrefixMatcher matcher: implList) {
                System.out.println(matcher.obtainMatchedWords("sh"));
            }
        }
    }
}

 

  ServiceLoader 的實現涉及到如下概念: 指向對象類型的 Class<S> 對象; 類加載器 ClassLoader; 服務實現類的資源抽象; 服務實現類的全名字符串。結合類加載器和資源抽象獲得服務實現類的全名字符串,再通過類加載器獲取 Class<S> 對象, 最后通過 Class<S> 對象來構造服務實現類 S 的實例 s 。

 

  ServiceLoader 的成員為 <Class<S> service, ClassLoader loader, LinkedHashMap<String,S> providers, LazyIterator lookupIterator>, 其中 service 是服務接口,loader 是類加載器, providers 是服務實現類的緩存, lookupIterator 是獲取服務實現類的迭代器,是 ServiceLoader 的內部類。 LazyIterator 的成員是 <Class<S> service, ClassLoader loader, Enumeration<URL> configs, Iterator<String> pending, String nextName>, configs 存放服務實現類的資源配置抽象, pending 存放服務實現類的全名字符串, nextName 是下一個可獲取的服務實現類的全名字符串。加載資源使用到 classLoader 的 getSystemResources 和 getResources 方法。Java里的資源抽象使用類 URL 來唯一標識,無論是本地文件 ( file:/// ) 還是網絡文件 (http(s):// )。由於要從文件或網絡讀取文本字符串,因此要使用 BufferedReader 。

 

  在 Java 中,Class<T> 和 ClassLoader 是造物之始。萬物皆是“某類T” 的存在物,而“某類T” 是“萬類之類 Class<T>” 的存在物,類別也是一種存在物,存在物即 Object。實例 t -> 類別 T -> 所有類別的抽象 Class<T> -> Object。要創造類別 T 的實例,先通過某種方式(ClassLoader)找到該物的“種子”(Class<T> 對象),然后通過該種子來創造具體的物 t。要生成一個 Integer 對象,先找到 Class<Integer> , 然后 newInstance 出 Integer 的實例。而造物也要有個規則,“女媧造物”和“凡人造物”,如果要造一模一樣的物種,必須先經由女媧造物,否則就會造成混亂(至少軟件中會出現問題)。在 Java 里就有 BootstrapClassLoader -> ExtClassLoader -> AppClassLoader -> CustomClassLoader 的先后規則。關於類加載器可參見 【《Java類加載器總結》《深入探討Java類加載器》】, 閱讀一個 ClassLoader 的實現。 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM