摘要: 在Dubbo可擴展機制實戰中,咱們瞭解了Dubbo擴展機制的一些概念,初探了Dubbo中LoadBalance的實現,並本身實現了一個LoadBalance。是否是以爲Dubbo的擴展機制很不錯呀,接下來,咱們就深刻Dubbo的源碼,一睹廬山真面目。java
在Dubbo可擴展機制實戰中,咱們瞭解了Dubbo擴展機制的一些概念,初探了Dubbo中LoadBalance的實現,並本身實現了一個LoadBalance。是否是以爲Dubbo的擴展機制很不錯呀,接下來,咱們就深刻Dubbo的源碼,一睹廬山真面目。api
ExtentionLoader是最核心的類,負責擴展點的加載和生命週期管理。咱們就以這個類開始吧。
Extension的方法比較多,比較經常使用的方法有:緩存
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type)
public T getExtension(String name)
public T getAdaptiveExtension()
比較常見的用法有:app
LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName)
RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension()
說明:在接下來展現的源碼中,我會將無關的代碼(好比日誌,異常捕獲等)去掉,方便你們閱讀和理解。框架
*1. getExtensionLoader方法
這是一個靜態工廠方法,入參是一個可擴展的接口,返回一個該接口的ExtensionLoader實體類。經過這個實體類,能夠根據name得到具體的擴展,也能夠得到一個自適應擴展。dom
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { // 擴展點必須是接口 if (!type.isInterface()) { throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } // 必需要有@SPI註解 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type without @SPI Annotation!"); } // 從緩存中根據接口獲取對應的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; } private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
*2. getExtension方法jvm
public T getExtension(String name) { 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; }
getExtention方法中作了一些判斷和緩存,主要的邏輯在createExtension方法中。咱們繼續看createExtention方法。函數
private T createExtension(String name) { // 根據擴展點名稱獲得擴展類,好比對於LoadBalance,根據random獲得RandomLoadBalance類 Class<?> clazz = getExtensionClasses().get(name); T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { // 使用反射調用nesInstance來建立擴展類的一個示例 EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // 對擴展類示例進行依賴注入 injectExtension(instance); // 若是有wrapper,添加wrapper Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; }
createExtension方法作了如下事情:
*1. 先根據name來獲得對應的擴展類。從ClassPath下META-INF
文件夾下讀取擴展點配置文件。ui
*2. 使用反射建立一個擴展類的實例this
*3. 對擴展類實例的屬性進行依賴注入,即IoC。
*4. 若是有wrapper,添加wrapper,即AoP。
下面咱們來重點看下這4個過程
*1. 根據name獲取對應的擴展類
先看代碼:
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; } // synchronized in 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()); } 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; }
過程很簡單,先從緩存中獲取,若是沒有,就從配置文件中加載。配置文件的路徑就是以前提到的:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
*2. 使用反射建立擴展實例
這個過程很簡單,使用clazz.newInstance())
來完成。建立的擴展實例的屬性都是空值。
*3. 擴展實例自動裝配
在實際的場景中,類之間都是有依賴的。擴展實例中也會引用一些依賴,好比簡單的Java類,另外一個Dubbo的擴展或一個Spring Bean等。依賴的狀況很複雜,Dubbo的處理也相對複雜些。咱們稍後會有專門的章節對其進行說明,如今,咱們只須要知道,Dubbo能夠正確的注入擴展點中的普通依賴,Dubbo擴展依賴或Spring依賴等。
*4. 擴展實例自動包裝
自動包裝就是要實現相似於Spring的AOP功能。Dubbo利用它在內部實現一些通用的功能,好比日誌,監控等。關於擴展實例自動包裝的內容,也會在後面單獨講解。
通過上面的4步,Dubbo就建立並初始化了一個擴展實例。這個實例的依賴被注入了,也根據須要被包裝了。到此爲止,這個擴展實例就能夠被使用了。
自動裝配的相關代碼在injectExtension方法中:
private T injectExtension(T instance) { for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { Class<?> pt = method.getParameterTypes()[0]; 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); } } } return instance; }
要實現對擴展實例的依賴的自動裝配,首先須要知道有哪些依賴,這些依賴的類型是什麼。Dubbo的方案是查找Java標準的setter方法。即方法名以set開始,只有一個參數。若是擴展類中有這樣的set方法,Dubbo會對其進行依賴注入,相似於Spring的set方法注入。
可是Dubbo中的依賴注入比Spring要複雜,由於Spring注入的都是Spring bean,都是由Spring容器來管理的。而Dubbo的依賴注入中,須要注入的多是另外一個Dubbo的擴展,也多是一個Spring Bean,或是Google guice的組件,或其餘任何一個框架中的組件。Dubbo須要可以從任何一個場景中加載擴展。在injectExtension方法中,是用Object object = objectFactory.getExtension(pt, property)
來實現的。objectFactory是ExtensionFactory類型的,在建立ExtensionLoader時被初始化:
private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
objectFacory自己也是一個擴展,經過ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())
來獲取。
ExtensionLoader有三個實現:
*1. SpiExtensionLoader:Dubbo本身的Spi去加載Extension
*2. SpringExtensionLoader:從Spring容器中去加載Extension
*3. AdaptiveExtensionLoader: 自適應的AdaptiveExtensionLoader
這裏要注意AdaptiveExtensionLoader,源碼以下:
@Adaptive public class AdaptiveExtensionFactory implements 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); } 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; } }
AdaptiveExtensionLoader類有@Adaptive註解。前面提到了,Dubbo會爲每個擴展建立一個自適應實例。若是擴展類上有@Adaptive,會使用該類做爲自適應類。若是沒有,Dubbo會爲咱們建立一個。因此ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())
會返回一個AdaptiveExtensionLoader實例,做爲自適應擴展實例。
AdaptiveExtentionLoader會遍歷全部的ExtensionFactory實現,嘗試着去加載擴展。若是找到了,返回。若是沒有,在下一個ExtensionFactory中繼續找。Dubbo內置了兩個ExtensionFactory,分別從Dubbo自身的擴展機制和Spring容器中去尋找。因爲ExtensionFactory自己也是一個擴展點,咱們能夠實現本身的ExtensionFactory,讓Dubbo的自動裝配支持咱們自定義的組件。好比,咱們在項目中使用了Google的guice這個IoC容器。咱們能夠實現本身的GuiceExtensionFactory,讓Dubbo支持從guice容器中加載擴展。
在用Spring的時候,咱們常常會用到AOP功能。在目標類的方法先後插入其餘邏輯。好比一般使用Spring AOP來實現日誌,監控和鑑權等功能。
Dubbo的擴展機制,是否也支持相似的功能呢?答案是yes。在Dubbo中,有一種特殊的類,被稱爲Wrapper類。經過裝飾者模式,使用包裝類包裝原始的擴展點實例。在原始擴展點實現先後插入其餘邏輯,實現AOP功能。
那什麼樣類的纔是Dubbo擴展機制中的Wrapper類呢?Wrapper類是一個有複製構造函數的類,也是典型的裝飾者模式。下面就是一個Wrapper類:
class A{ private A a; public A(A a){ this.a = a; } }
類A有一個構造函數public A(A a)
,構造函數的參數是A自己。這樣的類就能夠成爲Dubbo擴展機制中的一個Wrapper類。Dubbo中這樣的Wrapper類有ProtocolFilterWrapper, ProtocolListenerWrapper等, 你們能夠查看源碼加深理解。
在Dubbo中Wrapper類也是一個擴展點,和其餘的擴展點同樣,也是在META-INF
文件夾中配置的。好比前面舉例的ProtocolFilterWrapper和ProtocolListenerWrapper就是在路徑dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
中配置的:
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper mock=com.alibaba.dubbo.rpc.support.MockProtocol
在Dubbo加載擴展配置文件時,有一段以下的代碼:
try { clazz.getConstructor(type); Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } catch (NoSuchMethodException e) {}
這段代碼的意思是,若是擴展類有複製構造函數,就把該類存起來,供之後使用。有複製構造函數的類就是Wrapper類。經過clazz.getConstructor(type)
來獲取參數是擴展點接口的構造函數。注意構造函數的參數類型是擴展點接口,而不是擴展類。
以Protocol爲例。配置文件dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
中定義了filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
。
ProtocolFilterWrapper代碼以下:
public class ProtocolFilterWrapper implements Protocol { private final Protocol protocol; // 有一個參數是Protocol的複製構造函數 public ProtocolFilterWrapper(Protocol protocol) { if (protocol == null) { throw new IllegalArgumentException("protocol == null"); } this.protocol = protocol; }
ProtocolFilterWrapper有一個構造函數public ProtocolFilterWrapper(Protocol protocol)
,參數是擴展點Protocol,因此它是一個Dubbo擴展機制中的Wrapper類。ExtensionLoader會把它緩存起來,供之後建立Extension實例的時候,使用這些包裝類依次包裝原始擴展點。
前面講到過,Dubbo須要在運行時根據方法參數來決定該使用哪一個擴展,因此有了擴展點自適應實例。實際上是一個擴展點的代理,將擴展的選擇從Dubbo啓動時,延遲到RPC調用時。Dubbo中每個擴展點都有一個自適應類,若是沒有顯式提供,Dubbo會自動爲咱們建立一個,默認使用Javaassist。
先來看下建立自適應擴展類的代碼:
public T getAdaptiveExtension() { Object instance = cachedAdaptiveInstance.get(); if (instance == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } } } return (T) instance; }
繼續看createAdaptiveExtension方法
private T createAdaptiveExtension() { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); }
繼續看getAdaptiveExtensionClass方法
private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
繼續看createAdaptiveExtensionClass方法,繞了一大圈,終於來到了具體的實現了。看這個createAdaptiveExtensionClass方法,它首先會生成自適應類的Java源碼,而後再將源碼編譯成Java的字節碼,加載到JVM中。
private Class<?> createAdaptiveExtensionClass() { String code = createAdaptiveExtensionClassCode(); ClassLoader classLoader = findClassLoader(); com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); }
Compiler的代碼,默認實現是javassist。
@SPI("javassist") public interface Compiler { Class<?> compile(String code, ClassLoader classLoader); }
createAdaptiveExtensionClassCode()方法中使用一個StringBuilder來構建自適應類的Java源碼。方法實現比較長,這裏就不貼代碼了。這種生成字節碼的方式也挺有意思的,先生成Java源代碼,而後編譯,加載到jvm中。經過這種方式,能夠更好的控制生成的Java類。並且這樣也不用care各個字節碼生成框架的api等。由於xxx.java文件是Java通用的,也是咱們最熟悉的。只是代碼的可讀性不強,須要一點一點構建xx.java的內容。
下面是使用createAdaptiveExtensionClassCode方法爲Protocol建立的自適應類的Java代碼範例:
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.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); } 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); } }
大體的邏輯和開始說的同樣,經過url解析出參數,解析的邏輯由@Adaptive的value參數控制,而後再根據獲得的擴展點名獲取擴展點實現,而後進行調用。若是你們想知道具體的構建.java代碼的邏輯,能夠看createAdaptiveExtensionClassCode
的完整實現。
在生成的Protocol$Adpative中,發現getDefaultPort和destroy方法都是直接拋出異常的,這是爲何呢?來看看Protocol的源碼:
@SPI("dubbo") public interface Protocol { int getDefaultPort(); @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; void destroy();
能夠看到Protocol接口中有4個方法,但只有export和refer兩個方法使用了@Adaptive註解。Dubbo自動生成的自適應實例,只有@Adaptive修飾的方法纔有具體的實現。因此,Protocol$Adpative類中,也只有export和refer這兩個方法有具體的實現,其他方法都是拋出異常。
本文爲雲棲社區原創內容,未經容許不得轉載。