SPI是Java JDK內部提供的一種服務發現機制。java
SPI->Service Provider Interface,服務提供接口,是Java JDK內置的一種服務發現機制spring
經過在ClassPath路徑下的META-INF/services文件夾查找文件,自動加載文件裏所定義的類apache
[⚠️注意事項]:
面向對象的設計裏,通常推薦模塊之間基於接口編程,模塊之間不對實現類進行編碼。若是涉及實現類就會違反可插拔的原則,針對於模塊裝配,Java SPI提供了爲某個接口尋找服務的實現機制。編程
[2].在jar包的META-INF/services/目錄裏建立一個以服務接口命名的文件。其實就是實現該服務接口的具體實現類。緩存
提供一個目錄: META-INF/services/ 放到ClassPath下面
[3].當外部程序裝配這個模塊的時候,就能經過該Jar包META-INF/services/配置文件找到具體的實現類名,並裝載實例化,完成模塊注入。springboot
目錄下放置一個配置文件: 文件名是須要拓展的接口全限定名稱 文件內部爲要實現的接口實現類 文件必須爲UTF-8編碼
[4].尋找服務接口實現,不用在代碼中提供,而是利用JDK提供服務查找工具類:java.util.ServiceLoader類來加載使用:app
ServiceLoader.load(xxx.class) ServiceLoader<XXXInterface> loads = ServiceLoader.load(xxx.class)
[1].ServiceLoader源碼:
ide
package java.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.security.AccessController; import java.security.AccessControlContext; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; public final class ServiceLoader<S> implements Iterable<S> { //[1].初始化定義全局配置文件路徑Path private static final String PREFIX = "META-INF/services/"; //[2].初始化定義加載的服務類或接口 private final Class<S> service; //[3].初始化定義類加載器 private final ClassLoader loader; //[4].初始化定義訪問控制上下文 private final AccessControlContext acc; //[5].初始化定義加載服務類的緩存集合 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); //[6].初始化定義私有內部LazyIterator類,真正加載服務類的實現類 private LazyIterator lookupIterator; //私有化有參構造-> ServiceLoader(Class<S> svc, ClassLoader cl) private ServiceLoader(Class<S> svc, ClassLoader cl) { //[1].實例化服務接口->Class<S> service = Objects.requireNonNull(svc, "Service interface cannot be null"); //[2].實例化類加載器->ClassLoader loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; //[3].實例化訪問控制上下文->AccessControlContext acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; //[4].回調函數->reload reload(); } public void reload() { //[1].清空緩存實例集合 providers.clear(); //[2].實例化私有內部LazyIterator類->LazyIterator lookupIterator = new LazyIterator(service, loader); } public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) { return new ServiceLoader<>(service, loader); } public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } }
2.LazyIterator源碼:
函數
private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; 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(); } }
[1].Dubbo SPI 機制:工具
META-INF/dubbo.internal/xxx=接口全限定名
Dubbo 並未使用 Java SPI,而是從新實現了一套功能更強的 SPI 機制。
Dubbo SPI 的相關邏輯被封裝在了 ExtensionLoader 類中,經過 ExtensionLoader,咱們能夠加載指定的實現類。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路徑下。
與Java SPI 實現類配置不一樣,Dubbo SPI 是經過鍵值對的方式進行配置,這樣咱們能夠按需加載指定的實現類。另外,在測試 Dubbo SPI 時,須要在 Robot 接口上標註 @SPI 註解。
[2].Cache SPI 機制:
META-INF/service/javax.cache.spi.CachingProvider=xxx
[3]Spring SPI 機制:
META-INF/services/org.apache.commons.logging.LogFactory=xxx
[4].SpringBoot SPI機制:
META-INF/spring.factories/org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx
在springboot的自動裝配過程當中,最終會加載META-INF/spring.factories文件,而加載的過程是由SpringFactoriesLoader加載的。從CLASSPATH下的每一個Jar包中搜尋全部META-INF/spring.factories配置文件,而後將解析properties文件,找到指定名稱的配置後返回
源碼:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; // spring.factories文件的格式爲:key=value1,value2,value3 // 從全部的jar包中找到META-INF/spring.factories文件 // 而後從文件中解析出key=factoryClass類名稱的全部value值 public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); // 取得資源文件的URL Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>(); // 遍歷全部的URL while (urls.hasMoreElements()) { URL url = urls.nextElement(); // 根據資源文件URL解析properties文件,獲得對應的一組@Configuration類 Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); String factoryClassNames = properties.getProperty(factoryClassName); // 組裝數據,並返回 result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; }
[5].自定義序列化實現SPI:META-INF/services/xxx=接口全限定名
參考學習Java SPI 和Dubbo SPI機制源碼,本身動手實現序列化工具類等