摘要:什么是類那什么樣類的才是擴展機制中的類呢類是一個有復制構(gòu)造函數(shù)的類,也是典型的裝飾者模式。代碼如下有一個參數(shù)是的復制構(gòu)造函數(shù)有一個構(gòu)造函數(shù),參數(shù)是擴展點,所以它是一個擴展機制中的類。
摘要:?在Dubbo可擴展機制實戰(zhàn)中,我們了解了Dubbo擴展機制的一些概念,初探了Dubbo中LoadBalance的實現(xiàn),并自己實現(xiàn)了一個LoadBalance。是不是覺得Dubbo的擴展機制很不錯呀,接下來,我們就深入Dubbo的源碼,一睹廬山真面目。
在Dubbo可擴展機制實戰(zhàn)中,我們了解了Dubbo擴展機制的一些概念,初探了Dubbo中LoadBalance的實現(xiàn),并自己實現(xiàn)了一個LoadBalance。是不是覺得Dubbo的擴展機制很不錯呀,接下來,我們就深入Dubbo的源碼,一睹廬山真面目。
ExtensionLoader
ExtentionLoader是最核心的類,負責擴展點的加載和生命周期管理。我們就以這個類開始吧。
Extension的方法比較多,比較常用的方法有:
public static
public T getExtension(String name)
public T getAdaptiveExtension()
比較常見的用法有:
LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName)
RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension()
說明:在接下來展示的源碼中,我會將無關的代碼(比如日志,異常捕獲等)去掉,方便大家閱讀和理解。
*1. getExtensionLoader方法
這是一個靜態(tài)工廠方法,入?yún)⑹且粋€可擴展的接口,返回一個該接口的ExtensionLoader實體類。通過這個實體類,可以根據(jù)name獲得具體的擴展,也可以獲得一個自適應擴展。
public static
// 擴展點必須是接口 if (!type.isInterface()) { throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } // 必須要有@SPI注解 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type without @SPI Annotation!"); } // 從緩存中根據(jù)接口獲取對應的ExtensionLoader // 每個擴展只會被加載一次 ExtensionLoaderloader = (ExtensionLoader ) EXTENSION_LOADERS.get(type); if (loader == null) { // 初始化擴展 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader (type)); loader = (ExtensionLoader ) 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方法
public T getExtension(String name) {
Holder
getExtention方法中做了一些判斷和緩存,主要的邏輯在createExtension方法中。我們繼續(xù)看createExtention方法。
private T createExtension(String name) {
// 根據(jù)擴展點名稱得到擴展類,比如對于LoadBalance,根據(jù)random得到RandomLoadBalance類 Class> clazz = getExtensionClasses().get(name); T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { // 使用反射調(diào)用nesInstance來創(chuàng)建擴展類的一個示例 EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // 對擴展類示例進行依賴注入 injectExtension(instance); // 如果有wrapper,添加wrapper Set> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance;
}
createExtension方法做了以下事情:
*1. 先根據(jù)name來得到對應的擴展類。從ClassPath下META-INF文件夾下讀取擴展點配置文件。
*2. 使用反射創(chuàng)建一個擴展類的實例
*3. 對擴展類實例的屬性進行依賴注入,即IoC。
*4. 如果有wrapper,添加wrapper,即AoP。
下面我們來重點看下這4個過程
*1. 根據(jù)name獲取對應的擴展類
先看代碼:
private Map
Map> 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 > 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 > extensionClasses = new HashMap >(); 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. 使用反射創(chuàng)建擴展實例
這個過程很簡單,使用clazz.newInstance())來完成。創(chuàng)建的擴展實例的屬性都是空值。
*3. 擴展實例自動裝配
在實際的場景中,類之間都是有依賴的。擴展實例中也會引用一些依賴,比如簡單的Java類,另一個Dubbo的擴展或一個Spring Bean等。依賴的情況很復雜,Dubbo的處理也相對復雜些。我們稍后會有專門的章節(jié)對其進行說明,現(xiàn)在,我們只需要知道,Dubbo可以正確的注入擴展點中的普通依賴,Dubbo擴展依賴或Spring依賴等。
*4. 擴展實例自動包裝
自動包裝就是要實現(xiàn)類似于Spring的AOP功能。Dubbo利用它在內(nèi)部實現(xiàn)一些通用的功能,比如日志,監(jiān)控等。關于擴展實例自動包裝的內(nèi)容,也會在后面多帶帶講解。
經(jīng)過上面的4步,Dubbo就創(chuàng)建并初始化了一個擴展實例。這個實例的依賴被注入了,也根據(jù)需要被包裝了。到此為止,這個擴展實例就可以被使用了。
Dubbo SPI高級用法之自動裝配
自動裝配的相關代碼在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;
}
要實現(xiàn)對擴展實例的依賴的自動裝配,首先需要知道有哪些依賴,這些依賴的類型是什么。Dubbo的方案是查找Java標準的setter方法。即方法名以set開始,只有一個參數(shù)。如果擴展類中有這樣的set方法,Dubbo會對其進行依賴注入,類似于Spring的set方法注入。
但是Dubbo中的依賴注入比Spring要復雜,因為Spring注入的都是Spring bean,都是由Spring容器來管理的。而Dubbo的依賴注入中,需要注入的可能是另一個Dubbo的擴展,也可能是一個Spring Bean,或是Google guice的組件,或其他任何一個框架中的組件。Dubbo需要能夠從任何一個場景中加載擴展。在injectExtension方法中,是用Object object = objectFactory.getExtension(pt, property)來實現(xiàn)的。objectFactory是ExtensionFactory類型的,在創(chuàng)建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有三個實現(xiàn):
*1. SpiExtensionLoader:Dubbo自己的Spi去加載Extension
*2. SpringExtensionLoader:從Spring容器中去加載Extension
*3. AdaptiveExtensionLoader: 自適應的AdaptiveExtensionLoader
這里要注意AdaptiveExtensionLoader,源碼如下:
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final Listfactories; public AdaptiveExtensionFactory() { ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class); List list = new ArrayList (); for (String name : loader.getSupportedExtensions()) { list.add(loader.getExtension(name)); } factories = Collections.unmodifiableList(list); } public T getExtension(Class type, String name) { for (ExtensionFactory factory : factories) { T extension = factory.getExtension(type, name); if (extension != null) { return extension; } } return null; }
}
AdaptiveExtensionLoader類有@Adaptive注解。前面提到了,Dubbo會為每一個擴展創(chuàng)建一個自適應實例。如果擴展類上有@Adaptive,會使用該類作為自適應類。如果沒有,Dubbo會為我們創(chuàng)建一個。所以ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())會返回一個AdaptiveExtensionLoader實例,作為自適應擴展實例。
AdaptiveExtentionLoader會遍歷所有的ExtensionFactory實現(xiàn),嘗試著去加載擴展。如果找到了,返回。如果沒有,在下一個ExtensionFactory中繼續(xù)找。Dubbo內(nèi)置了兩個ExtensionFactory,分別從Dubbo自身的擴展機制和Spring容器中去尋找。由于ExtensionFactory本身也是一個擴展點,我們可以實現(xiàn)自己的ExtensionFactory,讓Dubbo的自動裝配支持我們自定義的組件。比如,我們在項目中使用了Google的guice這個IoC容器。我們可以實現(xiàn)自己的GuiceExtensionFactory,讓Dubbo支持從guice容器中加載擴展。
Dubbo SPI高級用法之AoP
在用Spring的時候,我們經(jīng)常會用到AOP功能。在目標類的方法前后插入其他邏輯。比如通常使用Spring AOP來實現(xiàn)日志,監(jiān)控和鑒權(quán)等功能。
Dubbo的擴展機制,是否也支持類似的功能呢?答案是yes。在Dubbo中,有一種特殊的類,被稱為Wrapper類。通過裝飾者模式,使用包裝類包裝原始的擴展點實例。在原始擴展點實現(xiàn)前后插入其他邏輯,實現(xiàn)AOP功能。
什么是Wrapper類
那什么樣類的才是Dubbo擴展機制中的Wrapper類呢?Wrapper類是一個有復制構(gòu)造函數(shù)的類,也是典型的裝飾者模式。下面就是一個Wrapper類:
class A{
private A a; public A(A a){ this.a = a; }
}
類A有一個構(gòu)造函數(shù)public A(A a),構(gòu)造函數(shù)的參數(shù)是A本身。這樣的類就可以成為Dubbo擴展機制中的一個Wrapper類。Dubbo中這樣的Wrapper類有ProtocolFilterWrapper, ProtocolListenerWrapper等, 大家可以查看源碼加深理解。
怎么配置Wrapper類
在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
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet>(); wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {}
這段代碼的意思是,如果擴展類有復制構(gòu)造函數(shù),就把該類存起來,供以后使用。有復制構(gòu)造函數(shù)的類就是Wrapper類。通過clazz.getConstructor(type)來獲取參數(shù)是擴展點接口的構(gòu)造函數(shù)。注意構(gòu)造函數(shù)的參數(shù)類型是擴展點接口,而不是擴展類。
以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; // 有一個參數(shù)是Protocol的復制構(gòu)造函數(shù) public ProtocolFilterWrapper(Protocol protocol) { if (protocol == null) { throw new IllegalArgumentException("protocol == null"); } this.protocol = protocol; }
ProtocolFilterWrapper有一個構(gòu)造函數(shù)public ProtocolFilterWrapper(Protocol protocol),參數(shù)是擴展點Protocol,所以它是一個Dubbo擴展機制中的Wrapper類。ExtensionLoader會把它緩存起來,供以后創(chuàng)建Extension實例的時候,使用這些包裝類依次包裝原始擴展點。
擴展點自適應
前面講到過,Dubbo需要在運行時根據(jù)方法參數(shù)來決定該使用哪個擴展,所以有了擴展點自適應實例。其實是一個擴展點的代理,將擴展的選擇從Dubbo啟動時,延遲到RPC調(diào)用時。Dubbo中每一個擴展點都有一個自適應類,如果沒有顯式提供,Dubbo會自動為我們創(chuàng)建一個,默認使用Javaassist。
先來看下創(chuàng)建自適應擴展類的代碼:
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;
}
繼續(xù)看createAdaptiveExtension方法
private T createAdaptiveExtension() {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
}
繼續(xù)看getAdaptiveExtensionClass方法
private Class> getAdaptiveExtensionClass() {
getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
繼續(xù)看createAdaptiveExtensionClass方法,繞了一大圈,終于來到了具體的實現(xiàn)了。看這個createAdaptiveExtensionClass方法,它首先會生成自適應類的Java源碼,然后再將源碼編譯成Java的字節(jié)碼,加載到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的代碼,默認實現(xiàn)是javassist。
@SPI("javassist")
public interface Compiler {
Class> compile(String code, ClassLoader classLoader);
}
createAdaptiveExtensionClassCode()方法中使用一個StringBuilder來構(gòu)建自適應類的Java源碼。方法實現(xiàn)比較長,這里就不貼代碼了。這種生成字節(jié)碼的方式也挺有意思的,先生成Java源代碼,然后編譯,加載到jvm中。通過這種方式,可以更好的控制生成的Java類。而且這樣也不用care各個字節(jié)碼生成框架的api等。因為xxx.java文件是Java通用的,也是我們最熟悉的。只是代碼的可讀性不強,需要一點一點構(gòu)建xx.java的內(nèi)容。
下面是使用createAdaptiveExtensionClassCode方法為Protocol創(chuàng)建的自適應類的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解析出參數(shù),解析的邏輯由@Adaptive的value參數(shù)控制,然后再根據(jù)得到的擴展點名獲取擴展點實現(xiàn),然后進行調(diào)用。如果大家想知道具體的構(gòu)建.java代碼的邏輯,可以看createAdaptiveExtensionClassCode的完整實現(xiàn)。
在生成的Protocol$Adpative中,發(fā)現(xiàn)getDefaultPort和destroy方法都是直接拋出異常的,這是為什么呢?來看看Protocol的源碼:
@SPI("dubbo")
public interface Protocol {
int getDefaultPort(); @AdaptiveExporter export(Invoker invoker) throws RpcException; @Adaptive Invoker refer(Class type, URL url) throws RpcException; void destroy();
可以看到Protocol接口中有4個方法,但只有export和refer兩個方法使用了@Adaptive注解。Dubbo自動生成的自適應實例,只有@Adaptive修飾的方法才有具體的實現(xiàn)。所以,Protocol$Adpative類中,也只有export和refer這兩個方法有具體的實現(xiàn),其余方法都是拋出異常。
原文鏈接
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/69668.html
摘要:今天我想聊聊的另一個很棒的特性就是它的可擴展性。的擴展機制在的官網(wǎng)上,描述自己是一個高性能的框架。接下來的章節(jié)中我們會慢慢揭開擴展機制的神秘面紗。擴展擴展點的實現(xiàn)類。的定義在配置文件中可以看到文件中定義了個的擴展實現(xiàn)。 摘要: 在Dubbo的官網(wǎng)上,Dubbo描述自己是一個高性能的RPC框架。今天我想聊聊Dubbo的另一個很棒的特性, 就是它的可擴展性。 Dubbo的擴展機制 在Dub...
摘要:二注解該注解為了保證在內(nèi)部調(diào)用具體實現(xiàn)的時候不是硬編碼來指定引用哪個實現(xiàn),也就是為了適配一個接口的多種實現(xiàn),這樣做符合模塊接口設計的可插拔原則,也增加了整個框架的靈活性,該注解也實現(xiàn)了擴展點自動裝配的特性。 Dubbo擴展機制SPI 前一篇文章《dubbo源碼解析(一)Hello,Dubbo》是對dubbo整個項目大體的介紹,而從這篇文章開始,我將會從源碼來解讀dubbo再各個模塊的實...
摘要:而編碼器是講應用程序的數(shù)據(jù)轉(zhuǎn)化為網(wǎng)絡格式,解碼器則是講網(wǎng)絡格式轉(zhuǎn)化為應用程序,同時具備這兩種功能的單一組件就叫編解碼器。在中是老的編解碼器接口,而是新的編解碼器接口,并且已經(jīng)用把適配成了。 遠程通訊——開篇 目標:介紹之后解讀遠程通訊模塊的內(nèi)容如何編排、介紹dubbo-remoting-api中的包結(jié)構(gòu)設計以及最外層的的源碼解析。 前言 服務治理框架中可以大致分為服務通信和服務管理兩個...
摘要:英文全名為,也叫遠程過程調(diào)用,其實就是一個計算機通信協(xié)議,它是一種通過網(wǎng)絡從遠程計算機程序上請求服務而不需要了解底層網(wǎng)絡技術的協(xié)議。 Hello,Dubbo 你好,dubbo,初次見面,我想和你交個朋友。 Dubbo你到底是什么? 先給出一套官方的說法:Apache Dubbo是一款高性能、輕量級基于Java的RPC開源框架。 那么什么是RPC? 文檔地址:http://dubbo.a...
摘要:在中配置,以配置為例整個,最先使用的地方從里面讀取這個配置使用接口的中獲取具體的實現(xiàn)類中有兩個值當主線程被外部終止時,會觸發(fā),執(zhí)行的與方法通知下面的鎖操作,主線程正常走完代碼,并最終停止。 spring是如何啟動容器的 常見的一種在本地使用main方法啟動spring的方法 public static void main(String[] args) throws Except...
閱讀 1567·2021-11-19 09:55
閱讀 2792·2021-09-06 15:02
閱讀 3561·2019-08-30 15:53
閱讀 1107·2019-08-29 16:36
閱讀 1244·2019-08-29 16:29
閱讀 2296·2019-08-29 15:21
閱讀 634·2019-08-29 13:45
閱讀 2688·2019-08-26 17:15