SPI 全稱為 Service Provider Interface,是一種服務發(fā)現(xiàn)機制。當程序運行調(diào)用接口時,會根據(jù)配置文件或默認規(guī)則信息加載對應的實現(xiàn)類。所以在程序中并沒有直接指定使用接口的哪個實現(xiàn),而是在外部進行裝配。
要想了解 Dubbo 的設計與實現(xiàn),其中 Dubbo SPI 加載機制是必須了解的,在 Dubbo 中有大量功能的實現(xiàn)都是基于 Dubbo SPI 實現(xiàn)解耦,同時也使得 Dubbo 獲得如此好的可擴展性。
通過完成一個 Java SPI 的操作來了解它的機制。
創(chuàng)建一個 AnimalService 接口及 category 方法
創(chuàng)建一個實現(xiàn)類 Cat
創(chuàng)建 META-INF/services 目錄,并在該目錄下創(chuàng)建一個文件,文件名為 AnimalService 的全限定名作為文件名
在文件中添加實現(xiàn)類 Cat 的全限定名
Animal 接口
public interface AnimalService { void category();}
Cat 實現(xiàn)類
public class Cat implements AnimalService { @Override public void category() { System.out.println("cat: Meow ~"); }}
在 META-INF/services 目錄下的 top.ytao.demo.spi.AnimalService 文件中添加:
top.ytao.demo.spi.Cat
加載 SPI 的實現(xiàn):
public class JavaSPITest { @Test public void javaSPI() throws Exception { ServiceLoader<AnimalService> serviceLoader = ServiceLoader.load(AnimalService.class); // 遍歷在配置文件中已配置的 AnimalService 的所有實現(xiàn)類 for (AnimalService animalService : serviceLoader) { animalService.category(); } }}
執(zhí)行結果:
就這樣,一個 Java SPI 就實現(xiàn)完成了,通過 ServiceLoader.load
獲取加載所有接口已配置的接口實現(xiàn)類,然后可以遍歷找出需要的實現(xiàn)。
本文 Dubbo 版本為2.7.5
Dubbo SPI 相較于 Java SPI 更為強大,并且都是由自己實現(xiàn)的一套 SPI 機制。其中主要的改進和優(yōu)化:
相對于 Java SPI 一次性加載所有實現(xiàn),Dubbo SPI 是按需加載,只加載需要使用的實現(xiàn)類。同時帶有緩存支持。
更為詳細的擴展加載失敗信息。
增加了對擴展 IOC 和 AOP的支持。
Dubbo SPI 的配置文件放在 META-INF/dubbo 下面,并且實現(xiàn)類的配置方式采用 K-V 的方式,key 為實例化對象傳入的參數(shù),value 為擴展點實現(xiàn)類全限定名。例如 Cat 的配置文件內(nèi)容:
cat = top.ytao.demo.spi.Cat
Dubbo SPI 加載過程中,對 Java SPI 的目錄也是可以被兼容的。
同時需要在接口上增加 @SPI 注解,@SPI 中可以指定 key 值,加載 SPI 如下:
public class DubboSPITest { @Test public void dubboSPI(){ ExtensionLoader<AnimalService> extensionLoader = ExtensionLoader.getExtensionLoader(AnimalService.class); // 獲取擴展類實現(xiàn) AnimalService cat = extensionLoader.getExtension("cat"); System.out.println("Dubbo SPI"); cat.category(); }}
執(zhí)行結果如下:
獲取 ExtensionLoader 實例是通過上面 getExtensionLoader 方法,具體實現(xiàn)代碼:
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } // 檢查 type 必須為接口 if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } // 檢查接口是否有 SPI 注解 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } // 緩存中獲取 ExtensionLoader 實例 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { // 加載 ExtensionLoader 實例到緩存中 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader;}
上面獲取擴展類加載器過程主要是檢查傳入的 type 是否合法,以及從擴展類加載器緩存中是否存在當前類型的接口,如果不存在則添加當前接口至緩存中。ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS
是擴展類加載器的緩存,它是以接口作為 key, 擴展類加載器作為 value 進行緩存。
獲取擴展類對象的方法ExtensionLoader#getExtension
,在這里完成擴展對象的緩存及創(chuàng)建工作:
public T getExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } // 如果傳入的參數(shù)為 true ,則獲取默認擴展類對象操作 if ("true".equals(name)) { return getDefaultExtension(); } // 獲取擴展對象,Holder 里的 value 屬性保存著擴展對象實例 final Holder<Object> holder = getOrCreateHolder(name); Object instance = holder.get(); // 使用雙重檢查鎖 if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { // 創(chuàng)建擴展對象 instance = createExtension(name); holder.set(instance); } } } return (T) instance;}
獲取 holder 對象是從緩存ConcurrentMap<String, Holder<Object>> cachedInstances
中獲取,如果不存在,則以擴展名 key,創(chuàng)建一個 Holder 對象作為 value,設置到擴展對象緩存。
如果是新創(chuàng)建的擴展對象實例,那么 holder.get() 一定是 null ,擴展對象為空時,經(jīng)過雙重檢查鎖,創(chuàng)建擴展對象。
創(chuàng)建擴展對象過程:
private T createExtension(String name) { // 從全部擴展類中,獲取當前擴展名對應的擴展類 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); } // 向當前實例注入依賴 injectExtension(instance); // 獲取包裝擴展類緩存 Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { // 創(chuàng)建包裝擴展類實例,并向其注入依賴 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } // 初始化擴展對象 initExtension(instance); return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); }}
上面創(chuàng)建擴展過程中,里面有個 Wrapper 類,這里使用到裝飾器模式,該類是沒有具體的實現(xiàn),而是把通用邏輯進行抽象。
創(chuàng)建這個過程是從所有擴展類中獲取當前擴展名對應映射關系的擴展類,以及向當前擴展對象注入依賴。
獲取所有擴展類:
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;}
檢查普通擴展類緩存是否為空,如果不為空則重新加載,真正加載擴展類在loadExtensionClasses
中:
private static final String SERVICES_DIRECTORY = "META-INF/services/";private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";private Map<String, Class<?>> loadExtensionClasses() { // 獲取 @SPI 上的默認擴展名 cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap<>(); // 先加載 Dubbo 內(nèi)部的擴展類, 通過 Boolean 值控制 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true); // 由于 Dubbo 遷到 apache ,所以包名有變化,會替換之前的 alibaba 為 apache loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); return extensionClasses;}
上面獲取 @SPI 擴展名,以及指定要加載的文件。從上面靜態(tài)常量中,我們可以看到,Dubbo SPI 也是支持加載 Java SPI 的目錄,同時還加載 META-INF/dubbo/internal (該目錄為 Dubbo 的內(nèi)部擴展類目錄),在 loadDirectory 加載目錄配置文件。
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst) { // 獲取文件在項目中的路徑,如:META-INF/dubbo/top.ytao.demo.spi.AnimalService String fileName = dir + type; try { Enumeration<java.net.URL> urls = null; ClassLoader classLoader = findClassLoader(); // 加載內(nèi)部擴展類 if (extensionLoaderClassLoaderFirst) { ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader(); if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) { urls = extensionLoaderClassLoader.getResources(fileName); } } // 加載當前 fileName 文件 if(urls == null || !urls.hasMoreElements()) { if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } } if (urls != null) { // 迭代加載同名文件的內(nèi)容 while (urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); // 加載文件內(nèi)容 loadResource(extensionClasses, classLoader, resourceURL); } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", description file: " + fileName + ").", t); } }
這里獲取文件名后加載所有同名文件,然后迭代各個文件,逐個加載文件內(nèi)容。
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; // 整行讀取文件內(nèi)容 while ((line = reader.readLine()) != null) { // 獲取當前行中第一個 "#" 的位置索引 final int ci = line.indexOf('#'); // 如果當前行存在 "#",則去除 "#" 后的內(nèi)容 if (ci >= 0) { line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; // 獲取當前行 "=" 的索引 int i = line.indexOf('='); // 如果當前行存在 "=",將 "=" 左右的值分開復制給 name 和 line if (i > 0) { name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { // 加載擴展類 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); }}
上面代碼完成文件內(nèi)容加載和解析,接下來通過 loadClass
加載擴展類。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { // 檢查當前實現(xiàn)類是否實現(xiàn)了 type 接口 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."); } // 當前實現(xiàn)類是否有 Adaptive 注解 if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz); // 當前類是否為 Wrapper 包裝擴展類 } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else { // 嘗試當前類是否有無參構造方法 clazz.getConstructor(); if (StringUtils.isEmpty(name)) { // 如果 name 為空,則獲取 clazz 的 @Extension 注解的值,如果注解值也沒有,則使用小寫類名 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)) { // 緩存 擴展名和@Activate的緩存 cacheActivateClass(clazz, names[0]); for (String n : names) { // 緩存 擴展類和擴展名的緩存 cacheName(clazz, n); // 將 擴展類和擴展名 保存到extensionClasses 擴展名->擴展類 關系映射中 saveInExtensionClass(extensionClasses, clazz, n); } } }}
至此,getExtensionClasses() 加載擴展類方法分析完成,接下分析注入依賴 injectExtension() 方法。
private T injectExtension(T instance) { // if (objectFactory == null) { return instance; } try { for (Method method : instance.getClass().getMethods()) { // 遍歷當前擴展類的全部方法,如果當前方法不屬于 setter 方法, // 即不是以 'set'開頭的方法名,參數(shù)不是一個的,該方法訪問級別不是 public 的,則不往下執(zhí)行 if (!isSetter(method)) { continue; } // 當前方法是否添加了不要注入依賴的注解 if (method.getAnnotation(DisableInject.class) != null) { continue; } Class<?> pt = method.getParameterTypes()[0]; // 判斷當前參數(shù)是否屬于 八個基本類型或void if (ReflectUtils.isPrimitives(pt)) { continue; } try { // 通過屬性 setter 方法獲取屬性名 String property = getSetterProperty(method); // 獲取依賴對象 Object object = objectFactory.getExtension(pt, property); if (object != null) { // 設置依賴 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;}
通過遍歷擴展類所有方法,找到相對應的依賴,然后使用反射調(diào)用 settter 方法來進行設置依賴。
objectFactory 對象如圖:
其中找到相應依賴是在 SpiExtensionFactory 或 SpringExtensionFactory 中,同時,這兩個 Factory 保存在 AdaptiveExtensionFactory 中進行維護。
@Adaptivepublic class AdaptiveExtensionFactory implements ExtensionFactory { private final List<ExtensionFactory> factories; public AdaptiveExtensionFactory() { // ...... } @Override public <T> T getExtension(Class<T> type, String name) { // 通過遍歷匹配到 type->name 的映射 for (ExtensionFactory factory : factories) { T extension = factory.getExtension(type, name); if (extension != null) { return extension; } } return null; }}
以上是對 Dubbo SPI 擴展類簡單加載過程分析完成。
為 Dubbo 更加靈活的使一個接口不通過硬編碼加載擴展機制,而是通過使用過程中進行加載,Dubbo 的另一加載機制——自適應加載。
自適應加載機制使用 @Adaptive 標注:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})public @interface Adaptive { String[] value() default {};}
Adaptive 的值是一個數(shù)組,可以配置多個 key。初始化時,遍歷所有 key 進行匹配,如果沒有則匹配 @SPI 的值。
當 Adaptive 注解標注在類上時,則簡單對應該實現(xiàn)。如果注解標注在接口方法上時,則會根據(jù)參數(shù)動態(tài)生成代碼來獲取擴展點的實現(xiàn)。
類上注解處理還是比較好理解,方法上的注解加載相對比較有研讀性。通過調(diào)用ExtensionLoader#getAdaptiveExtension
來進行獲取擴展實現(xiàn)。
public T getAdaptiveExtension() { // 獲取實例化對象緩存 Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError != null) { throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } // 雙重檢查鎖后創(chuàng)建自適應擴展 synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { // 創(chuàng)建自適應擴展 instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t); } } } } return (T) instance;}private T createAdaptiveExtension() { try { // 獲取自適應擴展后,注入依賴 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); }}
上面代碼完成了擴展類對象是否存在緩存中,如果不存在,則通過創(chuàng)建自適應擴展,并將實例注入依賴后,設置在實例化后的自適應擴展對象中。
其中getAdaptiveExtensionClass
是比較核心的流程。
private Class<?> getAdaptiveExtensionClass() { // 加載全部擴展類 getExtensionClasses(); // 加載全部擴展類后,如果有 @Adaptive 標注的類,cachedAdaptiveClass 則一定不會為空 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } // 創(chuàng)建自適應擴展類 return cachedAdaptiveClass = createAdaptiveExtensionClass();}private Class<?> createAdaptiveExtensionClass() { // 生成自適應擴展代碼 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); // 獲取擴展類加載器 ClassLoader classLoader = findClassLoader(); // 獲取編譯器類型的實現(xiàn)類 org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); // 編譯代碼,返回該對象 return compiler.compile(code, classLoader);}
這里完成的工作主要是,加載全部擴展類,代表所有擴展接口類的實現(xiàn)類,在其加載過程中,如果有 @Adaptive 標注的類,會保存到 cachedAdaptiveClass 中。通過自動生成自適應擴展代碼,并被編譯后,獲取擴展類實例化對象。
上面編譯器類型是可以指定的,通過 compiler 進行指定,例如:<dubbo:application name="taomall-provider" compiler="jdk" />
,該編譯器默認使用 javassist 編譯器。
在 generate 方法中動態(tài)生成代碼:
public String generate() { // 檢查當前擴展接口的方法上是否有 Adaptive 注解 if (!hasAdaptiveMethod()) { throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!"); } // 生成代碼 StringBuilder code = new StringBuilder(); // 生成類的包名 code.append(generatePackageInfo()); // 生成類的依賴類 code.append(generateImports()); // 生成類的聲明信息 code.append(generateClassDeclaration()); // 生成方法 Method[] methods = type.getMethods(); for (Method method : methods) { code.append(generateMethod(method)); } code.append("}"); if (logger.isDebugEnabled()) { logger.debug(code.toString()); } return code.toString();}
上面是生成類信息的方法,生成設計原理是按照已設置好的模板,進行替換操作,生成類。具體信息不代碼很多,但閱讀還是比較簡單。
自適應加載機制,已簡單分析完,咋一眼看,非常復雜,但是了解整體結構和流程,再去細研的話,相對還是好理解。
從 Dubbo 設計來看,其良好的擴展性,比較重要的一點是得益于 Dubbo SPI 加載機制。在學習它的設計理念,對可擴展性方面的編碼思考也有一定的啟發(fā)。