經過本文的學習,能夠了解 Dubbo SPI 的特性及實現原理,但願對你們的開發設計有必定的啓發性。java
SPI 全稱爲 Service Provider Interface,是一種模塊間組件相互引用的機制。其方案一般是提供方將接口實現類的全名配置在classPath下的指定文件中,由調用方讀取並加載。這樣須要替換某個組件時,只須要引入新的JAR包並在其中包含新的實現類和配置文件便可,調用方的代碼無需任何調整。優秀的SPI框架可以提供單接口多實現類時的優先級選擇,由用戶指定選擇哪一個實現。spring
得益於這些能力,SPI對模塊間的可插拔機制和動態擴展提供了很是好的支撐。數據庫
本文將簡單介紹JDK自帶的SPI,分析SPI和雙親委派的關係,進而重點分析DUBBO的SPI機制;比較二者有何不一樣,DUBBO的SPI帶來了哪些額外的能力。apache
提供者在classPath或者jar包的META-INF/services/目錄建立以服務接口命名的文件,調用者經過java.util.ServiceLoader加載文件內容中指定的實現類。bootstrap
search示例接口數組
package com.example.studydemo.spi; public interface Search { void search(); }
文件搜索實現類緩存
package com.example.studydemo.spi; public class FileSearchImpl implements Search { @Override public void search() { System.out.println("文件搜索"); } }
數據庫搜索實現類架構
package com.example.studydemo.spi; public class DataBaseSearchImpl implements Search { @Override public void search() { System.out.println("數據庫搜索"); } }
文件內容爲:app
com.example.studydemo.spi.DataBaseSearchImpl com.example.studydemo.spi.FileSearchImpl
測試:框架
import java.util.ServiceLoader; public class JavaSpiTest { public static void main(String[] args) { ServiceLoader<Search> searches = ServiceLoader.load(Search.class); searches.forEach(Search::search); } }
結果爲:
2. 簡單分析
ServiceLoader做爲JDK提供的一個服務實現查找工具類,調用自身load方法加載Search接口的全部實現類,而後可使用for循環遍歷實現類進行方法調用。
有一個疑問:META-INF/services/目錄是硬編碼的嗎,其它路徑行不行?答案是不行。
跟進到ServiceLoader類中,第一行代碼就是private static final String PREFIX = 「META-INF/services/」,因此SPI配置文件只能放在classPath或者jar包的這個指定目錄下面。
ServiceLoader的文件載入路徑
public final class ServiceLoader<S> implements Iterable<S> { //硬編碼寫死了文件路徑 private static final String PREFIX = "META-INF/services/"; // The class or interface representing the service being loaded private final Class<S> service; // The class loader used to locate, load, and instantiate providers private final ClassLoader loader;
JDK SPI的使用比較簡單,作到了基本的加載擴展組件的功能,但有如下幾點不足:
基於類加載的雙親委派原則,由JDK內部加載的class默認應該歸屬於bootstrap類加載器,那麼SPI機制加載的class是否也屬於bootstrap呢 ?
答案是否認的,原生SPI機制經過ServiceLoader.load方法由外部指定類加載器,或者默認取Thread.currentThread().getContextClassLoader()線程上下文的類加載器,從而避免了class被載入bootstrap加載器。
雙親委派的本質涵義是在rt.jar包和外部class之間創建一道classLoader的鴻溝,即rt.jar內的class不該由外部classLoader加載,外部class不該由bootstrap加載。
SPI僅是提供了一種在JDK代碼內部干預外部class文件加載的機制,並未強制指定加載到何處;外部的class仍是由外部的classLoader加載,未跨越這道鴻溝,也就談不上破壞雙親委派。
原生ServiceLoader的類加載器
//指定類加載器 public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) //默認取前線程上下文的類加載器 public static <S> ServiceLoader<S> load(Class<S> service)
Dubbo借鑑了Java SPI的思想,與JDK的ServiceLoader相對應的,Dubbo設計了ExtensionLoader類,其提供的功能比JDK更爲強大。
首先介紹一些基本概念,讓你們有一個初步的認知。
自適應擴展實例其實就是一個擴展類的代理對象,它實現了擴展點接口。在調用擴展點的接口方法時,會根據實際的參數來決定要使用哪一個擴展。好比一個Search的擴展點,有一個search方法。有兩個實現FileSearchImpl和DataBaseSearchImpl。Search的自適應實例在調用接口方法的時候,會根據search方法中的參數,來決定要調用哪一個Search的實現。
若是方法參數中有name=FileSearchImpl,那麼就調用FileSearchImpl的search方法。若是name=DataBaseSearchImpl,就調用DataBaseSearchImpl的search方法。 自適應擴展實例在Dubbo中的使用很是普遍。
在Dubbo中每個擴展點均可以有自適應的實例,若是咱們沒有使用@Adaptive人工指定,Dubbo會使用字節碼工具自動生成一個。
SPI Annotation
做用於擴展點的接口上,代表該接口是一個擴展點,能夠被Dubbo的ExtentionLoader加載
@Adaptive註解可使用在類或方法上。用在方法上表示這是一個自適應方法,Dubbo生成自適應實例時會在方法中植入動態代理的代碼。方法內部會根據方法的參數來決定使用哪一個擴展。@Adaptive註解用在類上表明該實現類是一個自適應類,屬於人爲指定的場景,Dubbo就不會爲該SPI接口生成代理類,最典型的應用如AdaptiveCompiler、AdaptiveExtensionFactory等。
@Adaptive註解的值爲字符串數組,數組中的字符串是key值,代碼中要根據key值來獲取對應的Value值,進而加載相應的extension實例。好比new String[]{「key1」,」key2」},表示會先在URL中尋找key1的值,
若是找到則使用此值加載extension,若是key1沒有,則尋找key2的值,若是key2也沒有,則使用SPI註解的默認值,若是SPI註解沒有默認值,則將接口名按照首字母大寫分紅多個部分,
而後以’.’分隔,例如org.apache.dubbo.xxx.YyyInvokerWrapper接口名會變成yyy.invoker.wrapper,而後以此名稱作爲key到URL尋找,若是仍沒有找到則拋出IllegalStateException異常。
定義一個接口,標註上dubbo的SPI註解,賦予默認值,並提供兩個extension實現類
package com.example.studydemo.spi; @SPI("dataBase") public interface Search { void search(); }
public class FileSearchImpl implements Search { @Override public void search() { System.out.println("文件搜索"); } }
public class DataBaseSearchImpl implements Search { @Override public void search() { System.out.println("數據庫搜索"); } }
在META-INF/dubbo 路徑下建立Search文件
文件內容以下:
dataBase=com.example.studydemo.spi.DataBaseSearchImpl file=com.example.studydemo.spi.FileSearchImpl
編寫測試類進行測試,內容以下:
public class DubboSpiTest { public static void main(String[] args) { ExtensionLoader<Search> extensionLoader = ExtensionLoader.getExtensionLoader(Search.class); Search fileSearch = extensionLoader.getExtension("file"); fileSearch.search(); Search dataBaseSearch = extensionLoader.getExtension("dataBase"); dataBaseSearch.search(); System.out.println(extensionLoader.getDefaultExtensionName()); Search defaultSearch = extensionLoader.getDefaultExtension(); defaultSearch.search(); } }
結果爲:
從代碼示例上來看,Dubbo SPI與Java SPI在這幾方面是相似的:
下面深刻到源碼看看SPI在Dubbo中是怎樣工做的,以Protocol接口爲例進行分析。
//一、獲得Protocol的擴展加載對象extensionLoader,由這個加載對象得到對應的自適應擴展類 Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); //二、根據擴展名獲取對應的擴展類 Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");
在獲取擴展實例前要先獲取Protocol接口的ExtensionLoader組件,經過ExtensionLoader來獲取相應的Protocol實例Dubbo實際是爲每一個SPI接口都建立了一個對應的ExtensionLoader。
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 interface!"); } if(!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } //EXTENSION_LOADERS爲ConcurrentMap,存儲Class對應的ExtensionLoader 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; }
EXTENSION_LOADERS是一個 ConcurrentMap,以接口Protocol爲key,以ExtensionLoader對象爲value;保存的是Protocol擴展的加載類,第一次加載的時候Protocol尚未本身的接口加載類,須要實例化一個。
再看new ExtensionLoader<T>(type) 這個操做,下面爲ExtensionLoader的構造方法:
rivate ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
每個ExtensionLoader都包含2個值:type和objectFactory,此例中type就是Protocol,objectFactory就是ExtensionFactory。
對於ExtensionFactory接口來講,它的加載類中objectFactory值爲null。
對於其餘的接口來講,objectFactory都是經過ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()來獲取;objectFactory的做用就是爲dubbo的IOC提供依賴注入的對象,能夠認爲是進程內多個組件容器的一個上層引用,
隨着這個方法的調用次數愈來愈多,EXTENSION_LOADERS 中存儲的 loader 也會愈來愈多。
自適應擴展類與IOC
獲得ExtensionLoader組件以後,再看如何得到自適應擴展實例。
public T getAdaptiveExtension() { //cachedAdaptiveInstance爲緩存的自適應對象,第一次調用時尚未建立自適應類,因此instance爲null Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if(createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { //建立自適應對象實例 instance = createAdaptiveExtension(); //將自適應對象放到緩存中 cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; }
首先從cachedAdaptiveInstance緩存中獲取,第一次調用時尚未相應的自適應擴展,須要建立自適應實例,建立後再將該實例放到cachedAdaptiveInstance緩存中。
建立自適應實例參考createAdaptiveExtension方法,該方法包含兩部份內容:建立自適應擴展類並利用反射實例化、利用IOC機制爲該實例注入屬性。
private T createAdaptiveExtension() { try { //獲得自適應擴展類並利用反射實例化,而後注入屬性值 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e); } }
再來分析getAdaptiveExtensionClass方法,以Protocol接口爲例,該方法會作如下事情:獲取全部實現Protocol接口的擴展類、若是有自適應擴展類直接返回、若是沒有則建立自適應擴展類。
//該動態代理生成的入口 private Class<?> getAdaptiveExtensionClass() { //1.獲取全部實現Protocol接口的擴展類 getExtensionClasses(); //2.若是有自適應擴展類,則返回 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } //3.若是沒有,則建立自適應擴展類 return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
getExtensionClasses方法會加載全部實現Protocol接口的擴展類,首先從緩存中獲取,緩存中沒有則調用loadExtensionClasses方法進行加載並設置到緩存中,以下圖所示:
private Map<String, Class<?>> getExtensionClasses() { //從緩存中獲取 Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { //從SPI配置文件中解析 classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; }
loadExtensionClasses方法以下:首先獲取SPI註解中的value值,做爲默認擴展名稱,在Protocol接口中SPI註解的value爲dubbo,所以DubboProtocol就是Protocol的默認實現擴展。其次加載三個配置路徑下的全部的Protocol接口的擴展實現。
// 此方法已經getExtensionClasses方法同步過。 private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if(defaultAnnotation != null) { String value = defaultAnnotation.value(); if(value != null && (value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); if(names.length > 1) { throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); } if(names.length == 1) cachedDefaultName = names[0]; } } Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); //分別從三個路徑加載 loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadFile(extensionClasses, DUBBO_DIRECTORY); loadFile(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; } 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/";
在加載配置路徑下的實現中,其中有一個須要關注的點,若是其中某個實現類上有Adaptive註解,說明用戶指定了自適應擴展類,那麼該實現類就會被賦給cachedAdaptiveClass,在getAdaptiveExtensionClass方法中會被直接返回。
若是該變量爲空,則須要經過字節碼工具來建立自適應擴展類。
private Class<?> createAdaptiveExtensionClass() { //生成類代碼 String code = createAdaptiveExtensionClassCode(); //找到類加載器 ClassLoader classLoader = findClassLoader(); //獲取編譯器實現類,此處爲AdaptiveCompiler,此類上有Adaptive註解 com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); //將類代碼編譯爲Class return compiler.compile(code, classLoader); }
createAdaptiveExtensionClass方法生成的類代碼以下:
package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.extension.ExtensionLoader; public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol { public void destroy() { throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); } public int getDefaultPort() { throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); } public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException { if (arg1 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg1; String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } }
由字節碼工具生成的類Protocol$Adpative在方法末尾調用了ExtensionLoader.getExtensionLoader(xxx).getExtension(extName)來知足adaptive的自適應動態特性。
傳入的extName就是從url中獲取的動態參數,用戶只須要在表明DUBBO全局上下文信息的URL中指定protocol參數的取值,adaptiveExtentionClass就能夠去動態適配不一樣的擴展實例。
再看屬性注入方法injectExtension,針對public的只有一個參數的set方法進行處理,利用反射進行方法調用來實現屬性注入,此方法是Dubbo SPI實現IOC功能的關鍵。
private T injectExtension(T instance) { try { if (objectFactory != null) { for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { Class<?> pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } catch (Exception e) { logger.error("fail to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance;
Dubbo IOC 是經過set方法注入依賴,Dubbo首先會經過反射獲取到實例的全部方法,而後再遍歷方法列表,檢測方法名是否具備set方法特徵。如有則經過ObjectFactory獲取依賴對象。
最後經過反射調用set方法將依賴設置到目標對象中。objectFactory在建立加載類ExtensionLoader的時候已經建立了,由於@Adaptive是打在類AdaptiveExtensionFactory上,因此此處就是AdaptiveExtensionFactory。
AdaptiveExtensionFactory持有全部ExtensionFactory對象的集合,dubbo內部默認實現的對象工廠是SpiExtensionFactory和SpringExtensionFactory,他們通過TreeSet排好序,查找順序是優先先從SpiExtensionFactory獲取,若是返回空在從SpringExtensionFactory獲取。
//有Adaptive註解說明該類是自適應類,不須要程序本身建立代理類 @Adaptive public class AdaptiveExtensionFactory implements ExtensionFactory { //factories擁有全部ExtensionFactory接口的實現對象 private final List<ExtensionFactory> factories; public AdaptiveExtensionFactory() { ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class); List<ExtensionFactory> list = new ArrayList<ExtensionFactory>(); for (String name : loader.getSupportedExtensions()) { list.add(loader.getExtension(name)); } factories = Collections.unmodifiableList(list); } //查找時會遍歷factories,順序優先從SpiExtensionFactory中獲取,再從SpringExtensionFactory中獲取,緣由爲初始化時getSupportedExtensions方法中使用TreeSet已經排序,見下圖 public <T> T getExtension(Class<T> type, String name) { for (ExtensionFactory factory : factories) { T extension = factory.getExtension(type, name); if (extension != null) { return extension; } } return null; } }
public Set<String> getSupportedExtensions() { Map<String, Class<?>> clazzes = getExtensionClasses(); return Collections.unmodifiableSet(new TreeSet<String>(clazzes.keySet())); }
雖然有過分設計的嫌疑,但咱們不得不佩服dubbo SPI設計的精巧。
擴展實例和AOP
getExtension方法比較簡單,重點在於createExtension方法,根據擴展名建立擴展實例。
public T getExtension(String name) { if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { return getDefaultExtension(); } Holder<Object> holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(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方法中的部份內容上文已經分析過了,getExtensionClasses方法獲取接口的全部實現類,而後經過name獲取對應的Class。緊接着經過clazz.newInstance()來實例化該實現類,調用injectExtension爲實例注入屬性。
private T createExtension(String name) { //getExtensionClasses方法以前已經分析過,獲取全部的擴展類,而後根據擴展名獲取對應的擴展類 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, (T) clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } //屬性注入 injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && wrapperClasses.size() > 0) { 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 + ") could not be instantiated: " + t.getMessage(), t); } }
在方法的最後有一段對於WrapperClass包裝類的處理邏輯,若是接口存在包裝類實現,那麼就會返回包裝類實例。實現AOP的關鍵就是WrapperClass機制,判斷一個擴展類是不是WrapperClass的依據,是看其constructor函數中是否包含當前接口參數。
若是有就認爲是一個wrapperClass,最終建立的實例是一個通過多個wrapperClass層層包裝的結果;在每一個wrapperClass中均可以編入面向切面的代碼,從而就簡單實現了AOP功能。
Activate活性擴展
對應ExtensionLoader的getActivateExtension方法,根據多個過濾條件從extension集合中智能篩選出您所需的那一部分。
getActivateExtension方法
public List<T> getActivateExtension(URL url, String[] names, String group);
首先這個方法只會返回帶有Activate註解的擴展類,但並不是帶有註解的擴展類都會被返回。
names是明確指定所須要的那部分擴展類,非明確指定的擴展類須要知足group過濾條件和Activate註解自己指定的key過濾條件,非明確指定的會按照Activate註解中指定的排序規則進行排序;
getActivateExtension的返回結果是上述兩種擴展類的總和。
Activate註解類
*/ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Activate { /** * Group過濾條件。 */ String[] group() default {}; /** * Key過濾條件。包含{@link ExtensionLoader#getActivateExtension}的URL的參數Key中有,則返回擴展。 */ String[] value() default {}; /** * 排序信息,能夠不提供。 */ String[] before() default {}; /** * 排序信息,能夠不提供。 */ String[] after() default {}; /** * 排序信息,能夠不提供。 */ int order() default 0; }
活性Extension最典型的應用是rpc invoke時獲取filter鏈條,各類filter有明確的執行優先級,同時也能夠人爲增添某些filter,filter還能夠根據服務提供者和消費者進行分組過濾。
Dubbo invoke獲取filter鏈條
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), Constants.SERVICE_FILTER_KEY, Constants.PROVIDER);
以TokenFilter爲例,其註解爲@Activate(group = Constants.PROVIDER, value = Constants.TOKEN_KEY),表示該過濾器只在服務提供方纔會被加載,同時會驗證註冊地址url中是否帶了token參數,若是有token表示服務端註冊時指明瞭要作token驗證,天然就須要加載該filter。
反之則不用加載;此filter加載後的執行邏輯則是從url中獲取服務端註冊時預設的token,再從rpc請求的attachments中獲取消費方設置的remote token,比較二者是否一致,若不一致拋出RPCExeption異常阻止消費方的正常調用。
Dubbo 全部的接口幾乎都預留了擴展點,根據用戶參數來適配不一樣的實現。若是想增長新的接口實現,只須要按照SPI的規範增長配置文件,並指向新的實現便可。
用戶配置的Dubbo屬性都會體如今URL全局上下文參數中,URL貫穿了整個Dubbo架構,是Dubbo各個layer組件間相互調用的紐帶。
總結一下 Dubbo SPI 相對於 Java SPI 的優點:
做者: Xie Xiaopeng