SPI 全稱為 (Service Provider Interface) ,是JDK內置的一種服務提供發現機制。 目前有不少框架用它來做服務的擴展發現, 簡單來說,它就是一種動態替換發現的機制, 舉個例子來說,有個接口,想運行時動態的給它添加實現,你只需要添加一個實現。
當服務的提供者提供了一種接口的實現之后,需要在classpath下的META-INF/services/目錄里創建一個以服務接口命名的文件,這個文件里的內容就是這個接口的具體的實現類。當其他的程序需要這個服務的時候,就可以通過查找這個jar包(一般都是以jar包做依賴)的META-INF/services/中的配置文件,配置文件中有接口的具體實現類名,可以根據這個類名進行加載實例化,就可以使用該服務了。JDK中查找服務的實現的工具類是:java.util.ServiceLoader。
SPI實例
定義接口
package org.cellphone.api; public interface DataSource { String getConnection(); }
Oracle廠商實現接口
package org.cellphone.oracle; import org.cellphone.api.DataSource; public class DataSourceImpl implements DataSource { @Override public String getConnection() { return "Oracle提供數據庫連接池"; } }
並在classpath下的META-INF/services/目錄下以接口全路徑名定義文件:org.cellphone.api.DataSource,文件內容為:
org.cellphone.oracle.DataSourceImpl
MySQL廠商實現接口
package org.cellphone.mysql; import org.cellphone.api.DataSource; public class DataSourceImpl implements DataSource { @Override public String getConnection() { return "MySQL提供數據庫連接池"; } }
並在classpath下的META-INF/services/目錄下以接口全路徑名定義文件:org.cellphone.api.DataSource,文件內容為:
org.cellphone.mysql.DataSourceImpl
ServiceLoader.load(DataSource.class)讀取廠商Oracle、MySQL提供jar包中的文件,ServiceLoader實現了Iterable接口可通過while for循環語句遍歷出所有實現。
package org.cellphone.invoker; import org.cellphone.api.DataSource; import java.util.ServiceLoader; public class Main { private static ServiceLoader<DataSource> loader = ServiceLoader.load(DataSource.class); public static void main(String[] args) { for (DataSource source : loader) { System.out.println(source.getConnection()); } } }
SPI源碼解析
// ServiceLoader實現了Iterable接口,可以遍歷所有的服務實現者 public final class ServiceLoader<S> implements Iterable<S> { // 查找配置文件的目錄 private static final String PREFIX = "META-INF/services/"; // 表示要被加載的服務的類或接口 private final Class<S> service; // 這個ClassLoader用來定位,加載,實例化服務提供者 private final ClassLoader loader; // 訪問控制上下文 private final AccessControlContext acc; // 緩存已經被實例化的服務提供者,按照實例化的順序存儲 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // The current lazy-lookup iterator private LazyIterator lookupIterator; // 重新加載,就相當於重新創建ServiceLoader了,用於新的服務提供者安裝到正在運行的Java虛擬機中的情況 public void reload() { // 清空緩存中所有已實例化的服務提供者 providers.clear(); // 新建一個迭代器,該迭代器會從頭查找和實例化服務提供者 lookupIterator = new LazyIterator(service, loader); } // 私有構造器 // 使用指定的類加載器和服務創建服務加載器 // 如果沒有指定類加載器,使用系統類加載器,就是應用類加載器。 private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); } // 解析失敗處理的方法 private static void fail(Class<?> service, String msg, Throwable cause) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ": " + msg, cause); } private static void fail(Class<?> service, String msg) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ": " + msg); } private static void fail(Class<?> service, URL u, int line, String msg) throws ServiceConfigurationError { fail(service, u + ":" + line + ": " + msg); } // 解析服務提供者配置文件中的一行 // 首先去掉注釋校驗,然后保存 // 返回下一行行號 // 重復的配置項和已經被實例化的配置項不會被保存 private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError { // 讀取一行 String ln = r.readLine(); if (ln == null) { return -1; } // #號代表注釋行 int ci = ln.indexOf('#'); if (ci >= 0) ln = ln.substring(0, ci); ln = ln.trim(); int n = ln.length(); if (n != 0) { if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) fail(service, u, lc, "Illegal configuration-file syntax"); int cp = ln.codePointAt(0); if (!Character.isJavaIdentifierStart(cp)) fail(service, u, lc, "Illegal provider-class name: " + ln); for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { cp = ln.codePointAt(i); if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) fail(service, u, lc, "Illegal provider-class name: " + ln); } if (!providers.containsKey(ln) && !names.contains(ln)) names.add(ln); } return lc + 1; } // 解析配置文件,解析指定的url配置文件 // 使用parseLine方法進行解析,未被實例化的服務提供者會被保存到緩存中去 private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError { InputStream in = null; BufferedReader r = null; ArrayList<String> names = new ArrayList<>(); try { in = u.openStream(); r = new BufferedReader(new InputStreamReader(in, "utf-8")); int lc = 1; while ((lc = parseLine(service, u, r, lc, names)) >= 0); } catch (IOException x) { fail(service, "Error reading configuration file", x); } finally { try { if (r != null) r.close(); if (in != null) in.close(); } catch (IOException y) { fail(service, "Error closing configuration file", y); } } return names.iterator(); } // Private inner class implementing fully-lazy provider lookup // 服務提供者查找的迭代器 private class LazyIterator implements Iterator<S> { // 服務提供者接口 Class<S> service; // 類加載器 ClassLoader loader; // 保存實現類的url Enumeration<URL> configs = null; // 保存實現類的全名 Iterator<String> pending = null; // 迭代器中下一個實現類的全名 String nextName = null; private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; } private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen } public boolean hasNext() { if (acc == null) { return hasNextService(); } else { PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } } public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } } public void remove() { throw new UnsupportedOperationException(); } } // 獲取迭代器 // 返回遍歷服務提供者的迭代器 // 以懶加載的方式加載可用的服務提供者 // 懶加載的實現是:解析配置文件和實例化服務提供者的工作由迭代器本身完成 public Iterator<S> iterator() { return new Iterator<S>() { // 按照實例化順序返回已經緩存的服務提供者實例 Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); } public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; } // 為指定的服務使用指定的類加載器來創建一個ServiceLoader public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); } // 使用線程上下文的類加載器來創建ServiceLoader public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } // 使用擴展類加載器為指定的服務創建ServiceLoader // 只能找到並加載已經安裝到當前Java虛擬機中的服務提供者,應用程序類路徑中的服務提供者將被忽略 public static <S> ServiceLoader<S> loadInstalled(Class<S> service) { ClassLoader cl = ClassLoader.getSystemClassLoader(); ClassLoader prev = null; while (cl != null) { prev = cl; cl = cl.getParent(); } return ServiceLoader.load(service, prev); } /** * Returns a string describing this service. * * @return A descriptive string */ public String toString() { return "java.util.ServiceLoader[" + service.getName() + "]"; } }
ServiceLoader不是實例化以后,就去讀取配置文件中的具體實現,並進行實例化。而是等到使用迭代器去遍歷的時候,才會加載對應的配置文件去解析,調用hasNext方法的時候會去加載配置文件進行解析,調用next方法的時候進行實例化並緩存。
所有的配置文件只會加載一次,服務提供者也只會被實例化一次,重新加載配置文件可使用reload方法。
SPI缺點
通過上面的解析,可以發現,我們使用SPI查找具體的實現的時候,需要遍歷所有的實現,並實例化,然后我們在循環中才能找到我們需要實現。這應該也是最大的缺點,需要把所有的實現都實例化了,即便我們不需要,也都給實例化了。