Dubbo—SPI及自適應擴展原理

2020-10-13 追逐仰望星空

推薦學習

引言

Dubbo雖然已交由apache管理,並且社區活躍度也不如SpringCloud,但也是國內應用比較廣泛的RPC框架,其背後的設計思想非常值得我們學習借鑑。鑑於Dubbo官方文檔對於基礎的使用配置已經講解的非常清楚了,這裡就不再贅述。本文章將基於Dubbo2.5.3版本的源碼做分析。而Dubbo中最核心的一點就是SPI和自適應擴展,Dubbo的高擴展性以及其它功能都是在這個基礎上實現的,理解掌握其原理才能看懂後面的一系列功能的實現原理,對我們平時實現高擴展性也非常有幫助。(PS:Dubbo源碼不像Zookeeper那樣有明確的入口,可以根據官網的源碼分析指導找到。)

正文

一、什麼是SPI?

SPI(Service Provider Interface)是一種服務發現機制,它的作用是解耦接口和其具體實現,讓各廠商可以自定義自己的擴展實現,並使得程序可以自動使用引入的組件。
什麼意思呢?舉個例子就清楚了,Java原生就提供SPI機制,比如資料庫連接驅動的實現就是SPI很好的一個應用,在Java sql下提供了Driver接口,而具體的驅動程序是由各個資料庫廠商實現的,平時我們要連接哪個資料庫只需要引入對應的驅動jar包就可以了,非常方便,即使資料庫變更也一樣,我們不需要改變代碼。而Dubbo的SPI機制則是在此基礎上提供了更強大的功能,因此,學習了解Java SPI更益於深入了解Dubbo,下面就先來看看Java SPI的使用吧。

1. Java SPI的實現

  • 首先定義一個服務接口和兩個自定義實現類(一般實現類會由第三方提供):

public interface SPI { void sayHello(String s);}public class SPIImpl1 implements SPI { @Override public void sayHello(String s) { System.out.println("Hello, " + s + "! I'm one"); }}public class SPIImpl2 implements SPI { @Override public void sayHello(String s) { System.out.println("Hello, " + s + "! I'm two"); }}

  • 然後在resources/META-INF/services創建一個以接口全類名為名稱的文件cn.dark.SPI,並在文件中填入自定義實現類的全類名

cn.dark.SPIImpl1cn.dark.SPIImpl2

  • 最後通過ServiceLoader類發現並調用服務:

ServiceLoader<SPI> load = ServiceLoader.load(SPI.class);for (SPI spi : load) { spi.sayHello("SPI");}

輸出:

Hello, SPI! I'm oneHello, SPI! I'm two

如果需要擴展新的實現,只需要將實現類配置到資源文件中,並引入對應的Jar即可。Java SPI機制就這麼簡單,其實現原理也很簡單,讀者們可以自行閱讀源碼,這裡就不再詳細分析了,那Dubbo的SPI有何異同呢?

2. Dubbo SPI實現原理

由配置文件得到的猜想

Dubbo SPI是基於Java原生SPI思想重新實現的一套更加強大的SPI機制,類似的你可以在Dubbo的META-INF.dubbo.internal(不止這一個路徑,後面在源碼中會看到)路徑下看到很多以接口全類名命名的配置文件,但是文件內容和JAVA SPI有點不一樣,以Protocol擴展為例:

registry=com.alibaba.dubbo.registry.integration.RegistryProtocolfilter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapperlistener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrappermock=com.alibaba.dubbo.rpc.support.MockProtocolinjvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocoldubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocolrmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocolhessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocolcom.alibaba.dubbo.rpc.protocol.http.HttpProtocolcom.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocolthrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocolmemcached=memcom.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocolredis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol

看到這麼多擴展類(每一個配置文件中都有很多),我們首先應該思考一個問題:Dubbo一啟動,就加載所有的擴展類麼?作為一個優秀的RPC框架,肯定不會耗時耗力做這樣的無用功,所以肯定會通過一種方式拿到指定的擴展才對。我們可以看到大多是以鍵值對方式(表示為extName-value)配置的擴展,那麼不難猜測,這裡的extName就是用來實現上面所說的功能的。
那到底是不是呢?以上純屬猜測,下面就到源碼中去驗證。

SPI源碼

Dubbo中實現SPI的核心類是ExtensionLoader,該類並未提供公共的構造方法來初始化,而是通過getExtensionLoader方法獲取一個loader對象:

// loader緩存private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, 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!"); } 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;}private final ExtensionFactory objectFactory;private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());}

這裡的class參數就是擴展點的接口類型,每一個loader都需要綁定一個擴展點類型。然後首先從緩存中獲取loader,未獲取到就初始化一個loader並放入緩存。而在私有構造器初始化的時候我們需要注意objectFactory這個變量,先大概有個映像,後面會用到。
拿到loader之後,就可以調用
getExtension方法去獲取指定的擴展點了,該方法傳入了一個name參數,不難猜測這個就是配置文件中的鍵,可以debugger驗證一下:

private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();public T getExtension(String name) { if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { return getDefaultExtension(); } // 從緩存中獲取Holder對象,該對象的值就是擴展對象 Holder<Object> holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } // 緩存中沒有該擴展,說明還沒有加載擴展類,就去配置文件中加載並創建對應的擴展對象 // 這裡通過雙重校驗鎖的方式保證線程安全,Dubbo中大量運用了該技巧 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 static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();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, (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); }}

關鍵點代碼就在getExtensionClasses方法中,怎麼從配置文件中加載擴展類的。而該方法主要是調用了loadExtensionClasses方法:

private Map<String, Class<?>> loadExtensionClasses() { // 判斷接口上是否標註有 @SPI註解,該註解的值就是默認使用的擴展類, // 賦值給cachedDefaultName變量緩存起來 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;}

該方法主要是緩存當前擴展接口指定的默認擴展實現類(@SPI註解指定),並調用loadFile讀取配置文件,從這裡我們可以看到Dubbo默認是讀取以下3個文件夾中的配置文件:

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/";

然後是loadFile,該方法很長,不全部放上來了,這裡提取關鍵的代碼:

String fileName = dir + type.getName();

首先通過文件全路徑找到對應的文件,並用BufferedReader一行行讀取文件內容:

String name = null;int i = line.indexOf('=');if (i > 0) { // 配置文件中的鍵 name = line.substring(0, i).trim(); // 擴展點全類名 line = line.substring(i + 1).trim();}if (line.length() > 0) { // 加載class,如果有些類的依賴jar包未導入,這裡就會拋出異常(比如WebserviceProtocol) Class<?> clazz = Class.forName(line, true, classLoader); // 驗證當前類型是否是擴展類的父類型 if (! type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface."); } // 擴展類是否標註了 @Adaptive 註解,表示為一個自定義的自適應擴展類 // 如果是將其緩存到cachedAdaptiveClass if (clazz.isAnnotationPresent(Adaptive.class)) { if(cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (! cachedAdaptiveClass.equals(clazz)) { // 超過一個自定義的自適應擴展類就拋出異常 throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } else { try { // 進入到該分支表示為Wrapper裝飾擴展類,該類都有一個特徵:包含 // 一個有參的構造器,如果沒有,就拋出異常進入到另一個分支, // Wrapper類的作用我們後面再分析 clazz.getConstructor(type); // 緩存Wrapper到cachedWrapperClasses中 Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } catch (NoSuchMethodException e) { // 進入此分支表示為一個普通的擴展類 clazz.getConstructor(); if (name == null || name.length() == 0) { // 由於歷史原因,Dubbo最開始配置文件中並不是以K-V來配置的 // 擴展點,而是會通過@Extension註解指定,所以這裡會通過 // 該註解去獲取到name name = findAnnotationName(clazz); // 由於@Extension廢棄使用,但配置文件中仍存在非K-V的配置, // 所以這裡是直接通過類名獲取簡單的name if (name == null || name.length() == 0) { if (clazz.getSimpleName().length() > type.getSimpleName().length() && clazz.getSimpleName().endsWith(type.getSimpleName())) { name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase(); } else { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url); } } } String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0) { // 判斷當前擴展點是否標註有@Activate註解,該註解表示 // 該擴展點自動激活 Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { cachedActivates.put(names[0], activate); } for (String n : names) { if (! cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); } Class<?> c = extensionClasses.get(n); if (c == null) { extensionClasses.put(n, clazz); } else if (c != clazz) { throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); } } } } }}

至此,我們就看到了Dubbo SPI的實現全過程,我們也了解了Dubbo強大的擴展性是如何實現的,但是這麼多擴展,Dubbo在運行中是如何決定調用哪一個擴展點的方法呢?這就是Dubbo另一強大的機制:自適應擴展。(PS:這裡需要留意cachedAdaptiveClasscachedWrapperClasses兩個變量的賦值,後面會用到。)

二、自適應擴展機制

什麼是自適應擴展?上文剛剛也說了,Dubbo中存在很多的擴展類,這些擴展類不可能一開始就全部初始化,那樣非常的耗費資源,所以我們應該在使用到該類的時候再進行初始化,也就是懶加載。但是這是比較矛盾的,拓展未被加載,那麼拓展方法就無法被調用(靜態方法除外)。拓展方法未被調用,拓展就無法被加載(官網原話)。所以也就有了自適應擴展機制,那麼這個原理是怎樣的呢?
首先需要了解@Adaptive註解,該註解可以標註在類和方法上:

  • 標註在類上,表明該類為自定義的適配類
  • 標註在方法上,表明需要動態的為該方法創建適配類

當有地方調用擴展類的方法時,首先會調用適配類的方法,然後適配類再根據擴展名稱調用getExtension方法拿到對應的擴展類對象,最後調用該對象的方法即可。流程就這麼簡單,下面看看代碼怎麼實現的。
首先我們回到ExtensionLoader的構造方法中:

objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

其中調用了getAdaptiveExtension方法,從方法名不難看出就是去獲取一個適配類對象:

private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();public T getAdaptiveExtension() { 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;}

該方法很簡單,就是從緩存中獲取適配類對象,未獲取到就調用createAdaptiveExtension方法加載適配類並通過反射創建對象:

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加載適配類:

private Class<?> getAdaptiveExtensionClass() { // 這裡剛剛分析過了,從配置文件中加載配置類 getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass();}

cachedAdaptiveClass這個變量應該還沒忘,在loadFile裡賦值的,即我們自定義的適配擴展類,若沒有則調用createAdaptiveExtensionClass動態創建:

private Class<?> createAdaptiveExtensionClass() { // 生成適配類的Java代碼,主要實現標註了@Adaptive的方法邏輯 String code = createAdaptiveExtensionClassCode(); ClassLoader classLoader = findClassLoader(); // 調用compiler編譯 com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader);}

該方法就是生成適配類的字節碼,你一定好奇適配類的代碼是怎樣的,只需要打斷點就可以看到了,這裡我們以Protocol類的適配類為例:

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) { 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) { 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擴展類都是通過export方法暴露服務,refer方法引用服務,而這兩個方法在接口中都標註了@Adaptive註解,所以Dubbo為其生成了動態的適配類(這個和Java的動態代理的原理有點像。同時我們看到這兩個方法中都通過getExtension方法去獲取指定的擴展類的實例(這個擴展類名稱來自於Invoker(後面會講)中的url,因為dubbo是基於url驅動的,所有的配置都在url中)。
這就是Dubbo強大的自適應擴展機制的實現原理,我們可以將其運用到我們的項目中去,這就是看源碼的好處。不過還有個問題,剛剛在
createAdaptiveExtensionClass方法中你一定疑惑compiler是什麼,它也是調用的getAdaptiveExtension獲取適配類,這不就進入了死循環麼?
當然不會,首先我們可以去配置文件看看Compiler的擴展類都有哪些:

adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompilerjdk=com.alibaba.dubbo.common.compiler.support.JdkCompilerjavassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler

有一個AdaptiveCompiler類,從名字上我們就能猜到它是一個自定義的適配類了,然後在其類上可以看到@Adaptive註解驗證我們的猜想,那麼上文也說了在loadFile方法中會將該類賦值給cachedAdaptiveClass變量緩存,然後在createAdaptiveExtension -> getAdaptiveExtensionClass方法中獲取並實例化對象,所以並不會死循環,那麼在該類中做了什麼呢?

public class AdaptiveCompiler implements Compiler { // 這個是在哪賦值的? private static volatile String DEFAULT_COMPILER; public static void setDefaultCompiler(String compiler) { DEFAULT_COMPILER = compiler; } public Class<?> compile(String code, ClassLoader classLoader) { Compiler compiler; ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); String name = DEFAULT_COMPILER; // copy reference if (name != null && name.length() > 0) { // 根據 DEFAULT_COMPILER 名稱獲取 compiler = loader.getExtension(name); } else { // 獲取@SPI註解值指定的默認擴展 compiler = loader.getDefaultExtension(); } return compiler.compile(code, classLoader); }}

該適配類會從兩個地方獲取擴展類,先來看看getDefaultExtension

public T getDefaultExtension() { getExtensionClasses(); if(null == cachedDefaultName || cachedDefaultName.length() == 0 || "true".equals(cachedDefaultName)) { return null; } return getExtension(cachedDefaultName);}

cachedDefaultName 這個不陌生吧,在loadExtensionClasses方法中賦值的,其值為@SPI的值,這裡就是javassist。再看另外一條分支,是通過DEFAULT_COMPILER的值去獲取的,這個變量提供了一個setter方法,點過去我們可以看到是在ApplicationConfig類中的setCompiler方法調用的,因為該類是配置類實例,也就是說可以通過dubbo:application的compiler參數來配置編譯器類型,查看文檔,也確實有這個配置參數。所以看源碼能讓我們了解平時項目中配置各個參數的意義,從而有針對的選擇和配置適當的參數,而不是一味的照搬文檔就完事。

三、Dubbo IOC

在上文中我們看到injectExtension這樣一個方法,它是做什麼的呢?接下來就詳細分析它的作用和實現。

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依賴注入的實現,從上面代碼中我們可以看出該方法是通過setter方法注入依賴擴展的(因為有些擴展點是需要依賴其它擴展點的,所以單單初始化當前擴展點還不行,還需要注入依賴的擴展):首先通過反射拿到參數的類型,然後從setter方法名中獲取到擴展點的名稱,最後從objectFactory中獲取依賴的擴展實例並通過反射注入。objectFactory這個參數還記得是什麼,怎麼初始化賦值的麼?這裡具體的實例對象是(不清楚怎麼來的忘記了就往上翻翻)AdaptiveExtensionFactory適配類類的對象,首先看看該類的初始化:

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);}

這裡不難理解,初始化時將所有的ExtensionFactory的擴展對象緩存到factories對象,然後在getExtension中循環,別分別調用它們的getExtension方法:

public <T> T getExtension(Class<T> type, String name) { // SpiExtensionFactory和SpringExtensionFactory for (ExtensionFactory factory : factories) { T extension = factory.getExtension(type, name); if (extension != null) { return extension; } } return null;}

ExtensionFactory的擴展配置文件中只有三個類:

adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactoryspi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactoryspring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory

除開上面的適配類,下面分別看看spi和spring做了哪些事:

public class SpiExtensionFactory implements ExtensionFactory { public <T> T getExtension(Class<T> type, String name) { // 判斷是否為@SPI標註的擴展接口 if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type); if (loader.getSupportedExtensions().size() > 0) { return loader.getAdaptiveExtension(); } } return null; }1234567891011121314

該類主要是獲取標了@SPI的擴展接口的適配類,其中getSupportedExtensions就是加載所有的擴展類。想一想ExtensionFactory本身就是被@SPI標註的,會在這裡再次返回適配類麼?
再來看
SpringExtensionFactory類:

public class SpringExtensionFactory implements ExtensionFactory { private static final Set<ApplicationContext> contexts = new ConcurrentHashSet<ApplicationContext>(); public static void addApplicationContext(ApplicationContext context) { contexts.add(context); } public static void removeApplicationContext(ApplicationContext context) { contexts.remove(context); } @SuppressWarnings("unchecked") public <T> T getExtension(Class<T> type, String name) { for (ApplicationContext context : contexts) { if (context.containsBean(name)) { Object bean = context.getBean(name); if (type.isInstance(bean)) { return (T) bean; } } } return null; }}

這個類也很簡單,就是從Spring IOC容器中返回對應的擴展對象。
以上就是Dubbo IOC的實現原理,非常簡單,但也很重要,我們通過idea快捷鍵可以看到只有以下兩處調用:
一個是
createExtension創建擴展類實例時:

injectExtension(instance);Set<Class<?>> wrapperClasses = cachedWrapperClasses;if (wrapperClasses != null && wrapperClasses.size() > 0) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); }}

另一個是createAdaptiveExtension創建適配類實例的時候:

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); }}

記住這兩個地方,後面再深入服務註冊調用時,時常會聯繫到這裡。

總結

今天這部分源碼我們可以從中看到Dubbo是如何是實現對擴展開放,對修改關閉以及如何優雅地使用設計模式的,今後在實際的Dubbo的使用中,也可以輕易的進行自定義擴展開發。最後我們可以想一想,之前的項目是否可以運用今天的所學進行重構呢?

作者:夜勿語

原文連結:https://blog.csdn.net/l6108003/article/details/100022896

相關焦點

  • Dubbo自適應擴展機制
    Dubbo SPI 特性Dubbo對Java中的標準SPI進行了擴展增強,官方文檔中提到其提供了如下特性:擴展點自動包裝擴展點自動裝配擴展點自適應擴展點自動激活在介紹各個特性前先介紹下大概的內部實現。返回實例前會遍歷所有的setXXX方法,判斷set方法參數是否存在自適應對象,如果存在則通過ExtensionLoader加載自適應對象然後進行賦值,可以通過方法上加org.apache.dubbo.common.extension.DisableInject註解來屏蔽該功能。
  • Dubbo SPI中AOP實現原理
    官網:http://dubbo.apache.org/zh-cn/docs/2.7/source_code_guide/dubbo-spi/從官網說明中我們可以看到,dubbo spi是java spi的一種增強實現,也就是這種spi機制確保了dubbo高擴展的能力。
  • 教你分析Dubbo源碼中的SPI和自適應擴展機制
    =com.wy.spi.PdfExport與Java SPI配置不同的是,Dubbo SPI 是通過健值對的方式進行配置的。SPIExtensionFactory: SPI 擴展點工廠Activate : 自動激活擴展點 註解Adaptive : 自適應擴展點 註解ExtensionFactory : 擴展點對象工廠ExtensionLoader: 擴展點加載器,核心類SPI 自定義擴展點 註解
  • 用最清晰明了的方式講述Dubbo的SPI機制
    Java SPI使用以及不足之處再說Dubbo SPI前,我們還是有必要先說一下Java的SPI機制(做一下預熱,因為Dubbo的SPI機制功能更加強大也更加複雜);對於Java SPI的使用是比較簡單的(Java SPI實現原理以後有時間的話可以給大家再說一下);1.首先我們肯定是要先定義擴展接口;public interface
  • 追蹤解析 Dubbo 的 Spi 機制源碼
    的封裝和擴展。import org.apache.dubbo.common.extension.Adaptive;import org.apache.dubbo.common.extension.SPI;@SPI("dubbo") // spi 最主要的註解public interface SpiDemoService { void
  • 高級Java程式設計師都必須要清楚的SPI服務擴展思想
    JDK中的SPI是通過在ClassPath路徑下的META-INF/services文件夾查找擴展文件,自動加載文件裡所定義的類。在小編的理解來,覺得它更是一種思想。即找到服務的接口, 美其名曰: 服務發現機制思想。很多開源框架都有借用這種思想,比如dubbo、jdbc。
  • Apache Dubbo介紹以及擴展機制SPI
    原理層面來說,他兩是一樣的;實現上來說,Dubbo SPI 是 Java SPI 的增強版。resource 創建一個名為 services 文件夾,然後創建一個名字為接口全限定名配置的文件 com.dubbo.demo.UserService。
  • Dubbo如何通過SPI提高框架的可擴展性?
    @Adaptive可以標記在類上或者方法上標記在類上:將該實現類直接作為默認實現,不再自動生成代碼標記在方法上:通過參數動態獲得實現類,比如上面的例子用源碼演示一下用在類上的@Adaptiv,Dubbo為自適應擴展點生成代碼
  • 突破Java面試- Dubbo負載均衡及動態代理的策略
    2 考點分析這些都是關於Dubbo必須知道,基本原理,序列化是什麼協議,具體用dubbo的時候,如何負載均衡,如何高可用,如何動態代理等.就是看你對Dubbo掌握程度工作原理:服務註冊,註冊中心,消費者,代理通信,負載均衡網絡通信、序列化:dubbo協議,長連接,NIO,hessian序列化協議負載均衡策略,集群容錯策略,動態代理策略:dubbo跑起來的時候一些功能是如何運轉的,怎麼做負載均衡?怎麼做集群容錯?怎麼生成動態代理?
  • dubbo系列之Filter
    項目中,我們可以自定義一個Filter來實現我們的業務需求,比如記錄入參出參,規範返回參數,並發控制,隱式傳輸數據等都可以基於Filter機制實現Dubbo默認的Filter在META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter文件中定義如何自定義一個Dubbo Filter1、我們先需要定義一個類來實現
  • 大廠面試系列(五):Dubbo和Spring Cloud
    Dubbo和Spring Cloud相關Dubbo你說你了解dubbo,能講一下dubbo的基本原理嗎? dubbo支持的通信協議和序列化協議? dubbo負載均衡和集群容錯策略有哪些?dubbo的spi思想dubbo進行的服務治理、服務降級、失敗以及重試。服務端怎麼知道客戶端要調用的算法的?闡述下dubbo的架構dubbo支持的註冊中心有哪些,分別的優缺點dubbo執行流程?dubbo和springclond的架構區別和優劣?說一下dubbo的實現過程?註冊中心掛了可以繼續通信嗎?
  • Apache Dubbo反序列化漏洞
    影響版本Dubbo 2.7.0 - 2.7.6 Dubbo 2.6.0 - 2.6.7 Dubbo 2.5.x (官方不再維護)環境搭建運行環境與編譯exp環境jdk版本均為8u121,啟動測試環境java -jar dubbo.jar
  • 跟我一起學Dubbo,SPI實戰與debug源碼,一文全搞定
    --暴露服務接口:多個服務接口實現類,均需要暴露--> <dubbo:service interface=&34; ref=&34;/> <dubbo:service interface=&34; ref=&34;/></beans>SPI配置文件新建META-INF/dubbo文件夾:注意建立子文件必須以
  • Java SPI 的原理與應用
    (給ImportNew加星標,提高Java技能)轉自:crossoverJie'blog  作者:crossoverJiehttps://crossoverjie.top/2020/02/24/wheel/cicada8-s
  • Dubbo 工作原理
    Dubbo 工作原理Dubbo 工作原理Dubbo 有10層結構:第一層:service 層,接口層,給服務提供者和消費者來實現的。第二層: config 層,配置層,主要是 dubbo 的各種配置。第三層: proxy 層,服務代理層,透明生成客戶端的 stub 和服務單的 skeleton第四層: registry 層,服務註冊層,負責服務的註冊於發現。第五層:cluster 層,集群層,封裝多個服務提供者的路由以及負載均衡。
  • 「DUBBO系列」線程模型實現原理與源碼分析
    :protocol name=&34; dispatcher=&34; /><dubbo:protocol name=&34; dispatcher=&34; /><dubbo:protocol name=&34; dispatcher=&34; /><dubbo:protocol name=&34; dispatcher=&34; /><dubbo:
  • 告訴你 Dubbo 的底層原理,面試不再怕!
    但是現在還是有不少公司沒有換成 SpringCloud 來做微服務的東西,還是基於 Dubbo,面試的時候不管是 SpringCloud 也好,Dubbo 也罷,基本上都會提到這兩個框架的底層原理。你想嘗試一下高級的職位,基本上跑不脫這個問題。OK,今兒我們就大概聊聊 Dubbo 的底層架構原理吧。
  • 阿里P7終於講完了JDK+Spring+mybatis+Dubbo+SpringMvc+Netty源碼
    dubbo源碼解析dubbo 是阿里基於開源思想 java 實現的服務治理中間件,目前除了阿里之外已有很多公司直接使用或者基於阿里開源版本擴展使用。大家是否熟悉 spi(service provider interface)機制,即我們定義了服務接口標準,讓廠商去實現(如果不了解 spi 的請谷歌百度下),jdk 通過 ServiceLoader 類實現 spi 機制的服務查找功能。
  • 「DUBBO系列」線程池策略實現原理與源碼分析
    請點擊【關注】獲取更多網際網路和技術乾貨,頭條號IT徐胖子原創本文請勿轉載,感謝支持1 文章概述本系列文章已經分析了DUBBO線程模型實現原理,本文簡單進行回顧。DUBBO提供了多種線程池策略,選擇線程池策略需要在配置文件指定threadpool屬性<dubbo:protocol name=&34; threadpool=&34; threads=&34; /><dubbo:protocol name=&34; threadpool=&34; threads=&34; /><dubbo:protocol
  • Dubbo簡介
    =&34;> <dubbo:application name=&34;/> <dubbo:registry address=&34;/> <dubbo:protocol name=&34;/> <bean id=&34; class=&34;/> <dubbo:service interface=&34; ref