最近在閱讀框架源代碼時,常常看到 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 的實現。