JDK SPI機制
SPI(Service Provider Interface),是一種將服務接口與服務實現分離以達到解耦可拔插、大大提升了程序可擴展性的機制。
約定(我覺得稱之為規范更合適):
1. 制定統一的規范(比如 java.sql.Driver)
2. 服務提供商提供這個規范具體的實現,在自己jar包的META-INF/services/目錄里創建一個以服務接口命名的文件,內容是實現類的全命名(比如:com.mysql.jdbc.Driver)。
3. 平台引入外部模塊的時候,就能通過該jar包META-INF/services/目錄下的配置文件找到該規范具體的實現類名,然后裝載實例化,完成該模塊的注入。
這個機制最大的優點就是無須在代碼里指定,進而避免了代碼污染,實現了模塊的可拔插。
JDK SPI的一個典型案例就是 java.sql.Driver 了

我們最熟悉的代碼
// 加載驅動 Class.forName("com.mysql.jdbc.Driver"); // 獲取連接 Connection connection = DriverManager.getConnection("url", "user", "password");
我們進入DriverManager類,里面有個靜態代碼塊,這部分的內容會先執行。
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }
加載並初始化驅動
private static void loadInitialDrivers() { // ... // 如果驅動被打包作為服務提供者,則加載它。 AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { // 1. load ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); // 2. 獲取Loader的迭代器 Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ // 3. 調用next方法 while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } }); // ... }
看 ServiceLoader#load方法
public static <S> ServiceLoader<S> load(Class<S> service) { // 獲取類加載器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); // load return ServiceLoader.load(service, cl); } public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){ // new 一個 ServiceLoader,參數為服務接口Class和類加載器 return new ServiceLoader<>(service, loader); }
可以看出,上面主要是獲取類加載器並新建ServiceLoader的過程,沒有加載實現類的動作。現在看:ServiceLoader#iterator 方法
public Iterator<S> iterator() { // 這里是個Iterator的匿名內部類,重寫了一些方法 return new Iterator<S>() { // 已存在的提供者 Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { // 先檢查緩存 if (knownProviders.hasNext()) return true; // 緩存沒有,走 java.util.ServiceLoader.LazyIterator#hasNext 方法 return lookupIterator.hasNext(); } public S next() { // 同樣先檢查緩存 if (knownProviders.hasNext()) return knownProviders.next().getValue(); // 緩存沒有,走 java.util.ServiceLoader.LazyIterator#next 方法 return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; }
看 ServiceLoader.LazyIterator#hasNext 方法
// java.util.ServiceLoader.LazyIterator#hasNext 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); } } private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { // 獲取全路徑:META-INF/services/java.sql.Driver 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; } // 解析的返回值是一個 Iterator<String> 類型,其中的String代表文件里配置的實現類全限定名,比如:com.mysql.jdbc.Driver pending = parse(service, configs.nextElement()); } // 把nextName初始化 nextName = pending.next(); return true; }
看 ServiceLoader.LazyIterator#next 方法
// java.util.ServiceLoader.LazyIterator#next 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); } } // 進入 java.util.ServiceLoader.LazyIterator#nextService 方法 private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); // com.mysql.jdbc.Driver String cn = nextName; nextName = null; Class<?> c = null; try { // 加載 com.mysql.jdbc.Driver Class 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 { // 實例化並轉換成 com.mysql.jdbc.Driver 對象 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 }
所以,DriverManager做了什么事:
// 1. 獲取類加載器並創建ServiceLoader對象 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); // 2. 創建迭代器並覆蓋hasNext和next方法 Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ // 3. 這個方法主要是讀取配置文件,獲取其中實現類的全限定名。 while(driversIterator.hasNext()) { // 4. 這個方法主要是根據全限定名去生成一個實例 driversIterator.next(); } } catch(Throwable t) { // Do nothing }
另外你也能發現,ServiceLoader只提供了遍歷的方式來獲取目標實現類,沒有提供按需加載的方法,這也是常說的不足之處。
自己實現
定義一個接口
package com.demo.spi; public interface Funny { void deal(); }
寫一個實現類
package com.demo.spi; public class FunnyImpl implements Funny { @Override public void deal() { System.out.println("FunnyImpl"); } }
配置

文件內容

執行測試
@Test public void test(){ ServiceLoader<Funny> serviceLoader = ServiceLoader.load(Funny.class); serviceLoader.forEach(Funny::deal); }
輸出

Spring SPI機制
對於Spring的SPI機制主要體現在SpringBoot中。我們知道SpringBoot的啟動包含new SpringApplication和執行run方法兩個過程,new的時候有這么個邏輯:(getSpringFactoriesInstances)

這個方法走到里面,無非 1. 加載類的全限定名列表。2. 根據類名通過反射實例化。

重點在於:SpringFactoriesLoader.loadFactoryNames(type, classLoader)
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { // 獲取類的全限定名 String factoryClassName = factoryClass.getName(); // 1. 執行loadSpringFactories,這里只傳入了類加載器,肯定是要獲取全部配置 // 2. getOrDefault,獲取指定接口的實現類名稱列表,如果沒有則返回一個空列表 return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { // 先檢查緩存 MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // 獲取類路徑下所有META-INF/spring.factories Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); // 把加載的配置轉換成Map<String, List<String>>格式 while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
它還提供了實例化的方法:SpringFactoriesLoader.loadFactories(factoryClass, classLoader) 利用反射實現,理解起來也不困難,不做解釋了
對比ServiceLoader:
1. 都是XXXLoader。命名格式都一樣。
2. 一個是加載 META-INF/services/ 目錄下的配置;一個是加載 META-INF/spring.factories 固定文件的配置。思路都一樣。
3. 兩個都是利用ClassLoader和ClassName來完成操作的。不同的是Java的ServiceLoader加載配置和實例化都是自己來實現,並且不能按需加載;SpringFactoriesLoader既可以單獨加載配置然后按需實例化也可以實例化全部。
如何實現一個Spring-Boot-Starter?
先看一下SpringBoot的相關要點。

這個是SpringBoot的 @SpringBootApplication 注解,里面還有一個 @EnableAutoConfiguration 注解,開啟自動配置的。

可以發現這個自動配置注解在另一個工程,而這個工程里也有個spring.factories文件,如下圖:

我們知道在SpringBoot的目錄下有個spring.factories文件,里面都是一些接口的具體實現類,可以由SpringFactoriesLoader加載。
那么同樣的道理,spring-boot-autoconfigure模塊也能動態加載了。看一下其中的內容:

我們看到有個接口【org.springframework.boot.autoconfigure.EnableAutoConfiguration】,有N個配置類,都是自動配置相關的,那么我們可以猜一猜,是不是模仿一下這樣的配置,我們就可以絲滑進入了?
為了證明這一點,我們看一下 dubbo-spring-boot-starter 的結構

你會發現starter項目沒有實現類,只有個pom文件。

它的關鍵在於引入了一個 autoconfigure 依賴。

這個里面配置了Spring的 spring.factories 其中只有一個配置

看到這里你是不是感覺到了什么:
1. 新建一個只有pom的starter工程,引入寫好的自動配置模塊。
2. 自動配置模塊配置 spring.factories 文件,格式如上。
3. 具體實現自動配置邏輯。
這樣當SpringBoot啟動加載配置類的時候,就會把這些第三方的配置一起加載。
所以我認為 Starter的作用就是在啟動之前把第三方的配置加載到容器中,具體表現就是無須用戶手動配置即可使用。【如果不准確望指正】
具體實現一個starter
兩個maven工程,目錄如下:

custom-spring-boot-autoconfigure↓↓↓↓↓
pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>custom-spring-boot-autoconfigure</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.1.6.RELEASE</version> </dependency> </dependencies> </project>
CustomAutoConfiguration
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet") public class CustomAutoConfiguration { @Bean public CustomConfig customConfig(){ System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!第三方自定義的配置"); return new CustomConfig(); } }
CustomConfig
/** * 自定義配置 */ public class CustomConfig { private String value = "Default"; }
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.CustomAutoConfiguration
一共四個文件
custom-spring-boot-starter↓↓↓↓
只有pom文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>custom-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.demo</groupId> <artifactId>custom-spring-boot-autoconfigure</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
最后把這兩個工程install

下面是新建一個SpringBoot項目,然后引入咱們自定義的starter依賴。
<dependency> <groupId>com.demo</groupId> <artifactId>custom-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
然后啟動就可以啦,可以看見,用戶引入依賴之后無需手動配置就可以使用第三方插件

看下工程依賴

Dubbo SPI機制
上面介紹了兩種SPI,到Dubbo這里就不難理解了吧?
思路都是處理類+約定配置 ,在Dubbo里,約定擴展配置在 META-INF/dubbo/internal/ 目錄下

在Dubbo源碼里也可以發現,核心類是 ExtensionLoader
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
getExtensionLoader
// 這個方法就是獲取一個特定類型的ExtensionLoader的實例 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } // 先從緩存中取 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { // 緩存沒有則新建 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
下面看getExtension方法
public T getExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } if ("true".equals(name)) { // 獲取默認的擴展類 return getDefaultExtension(); } // Holder用來持有目標對象 final Holder<Object> holder = getOrCreateHolder(name); // 獲取實例 Object instance = holder.get(); // 沒有的話,雙重檢查並創建實例 if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { instance = createExtension(name); holder.set(instance); } } } return (T) instance; }
看createExtension方法
private T createExtension(String name) { // 1. 獲取擴展Class Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { // 先從緩存中獲取 T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { // 沒有則新建 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // 2. IOC注入依賴 injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); } }
1. 獲取所有擴展類
private Map<String, Class<?>> getExtensionClasses() { // 緩存 + 雙重檢查 Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { // 加載擴展類 classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; } // 對於loadDirectory,每個目錄有兩個執行邏輯,因為目前要兼容alibaba老版本 private Map<String, Class<?>> loadExtensionClasses() { cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap<>(); // 加載目錄:META-INF/dubbo/internal/ loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); // 加載目錄:META-INF/dubbo/ loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); // 加載目錄:META-INF/services/ loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; } private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) { // 文件路徑,比如 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol String fileName = dir + type; try { Enumeration<java.net.URL> urls; ClassLoader classLoader = findClassLoader(); // 加載文件 if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { while (urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); // 加載資源(讀取文件內容) loadResource(extensionClasses, classLoader, resourceURL); } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", description file: " + fileName + ").", t); } } private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { try { try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) { String line; // 按行讀取 while ((line = reader.readLine()) != null) { // 判斷注釋 final int ci = line.indexOf('#'); if (ci >= 0) { // 截取 # 之前的字符串,# 之后的內容為注釋,需要忽略 line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; // 按照 = 分割,前面是name,后面是類全限定名 int i = line.indexOf('='); if (i > 0) { name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { // 加載Class loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); } } private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error occurred when loading extension class (interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + " is not subtype of interface."); } // 檢測目標類上是否有 Adaptive 注解 if (clazz.isAnnotationPresent(Adaptive.class)) { // 設置 cachedAdaptiveClass緩存 cacheAdaptiveClass(clazz); // 檢測 clazz 是否是 Wrapper 類型 } else if (isWrapperClass(clazz)) { // 存儲 clazz 到 cachedWrapperClasses 緩存中 cacheWrapperClass(clazz); } else { // 檢測 clazz 是否有默認的構造方法,如果沒有,則拋出異常 clazz.getConstructor(); if (StringUtils.isEmpty(name)) { // 如果 name 為空,則嘗試從 Extension 注解中獲取 name,或使用小寫的類名作為 name name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name); if (ArrayUtils.isNotEmpty(names)) { // 存儲 name 到 Activate 注解對象的映射關系 cacheActivateClass(clazz, names[0]); for (String n : names) { // 存儲 Class 到名稱的映射關系 cacheName(clazz, n); // 存儲名稱到 Class 的映射關系 saveInExtensionClass(extensionClasses, clazz, n); } } } }
這部分有些注釋直接拿的官網,總結下來這部分:
1. 根據約定的路徑比如【META-INF/dubbo/internal/】加載文件URl。
2. 讀取並解析每一個文件內容,具體為按行讀取,去掉注釋,按等號分割為 name-class全限定名鍵值對。
3. 根據類全限定名生成Class對象,根據Class對象的的特點進行相關的緩存以及name到Class對象的緩存。
2. IOC注入依賴
private T injectExtension(T instance) { try { if (objectFactory != null) { for (Method method : instance.getClass().getMethods()) { // 這里判斷方法是否是setter方法,Dubbo目前只處理setter的IOC if (isSetter(method)) { // 如果標注了@DisableInject,則不進行注入 if (method.getAnnotation(DisableInject.class) != null) { continue; } // 獲取 setter 方法參數類型 Class<?> pt = method.getParameterTypes()[0]; // 基本類型跳過 if (ReflectUtils.isPrimitives(pt)) { continue; } try { // 獲取屬性名,比如 setName 方法對應屬性名 name String property = getSetterProperty(method); // 從 ObjectFactory 中獲取依賴對象 Object object = objectFactory.getExtension(pt, property); if (object != null) { // 通過反射調用 setter 方法設置object依賴 method.invoke(instance, object); } } catch (Exception e) { logger.error("Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; }
這部分容易理解,核心部分利用反射實現,其它前置處理做了一些校驗。
而使用Dubbo SPI的話也是一樣的套路,擴展類實現 + 配置 + ExtensionLoader
