Dubbo的擴展機制
Dubbo擴展原理
ExtensionLoader的設計和實現方式
自定義擴展例子
- Dubbo的擴展原理
Dubbo採用的是內核+擴展
的體系結構,除了Service和Config層,其他層的功能都是可擴展的(Proxy、Registry、Cluster、Monitor、Protocol、Exchange、Transport、Serialize)。
也就是說,這些層的功能模塊,都可以通過配置的方式靈活地切換實現,並不需要修改框架的代碼。
Dubbo 使用 URL 總線模式(包含了Key-Value)傳遞配置信息,所有的狀態數據信息都可以從URL中解析獲取。
Dubbo 自身的功能也是通過擴展點實現的,也就是 Dubbo 的所有功能點都可被用戶自定義擴展所替換。
Dubbo本身也爲各層提供了多種實現,比如Registry,Dubbo就提供了MulticastRegistry、RedisRegistry、ZookeeperRegistry等實現。(使用Redis作爲註冊中心)。
Dubbo同樣支持用戶自定義的擴展實現。
Dubbo的擴展點由Java的SPI擴展點發現機制擴展而來。
自定義的擴展方式可以支持延遲加載,不用每次都一次性加載出所有的配置功能;Dubbo擴展還支持以key=value的方式進行配置;Dubbo擴展還支持輕量級的依賴注入,會將擴展點實現類中的另外的擴展點引用“順便”給初始化了。
- ExtensionLoader的設計和實現方式
擴展加載器,是Dubbo自定義擴展了SPI功能的加載器,它有一個getExtensionLoader
方法,用來獲取某個接口的ExtensionLoader
,每個接口只有一個該對象。
查看它提供的方法:
對外方法主要有3個類別,
activeExtension
adaptiveExtension
defaultExtension
除了需要提供獲取擴展的API,它主要關注如下的功能:
1、在運行時靈活適配不同的擴展實現
2、能支持擴展緩存,以避免重複加載
3、能自動裝配擴展的嵌套的情況 前面說過,每種功能Dubbo提供了多種實現,在使用的時候不能硬編碼決定使用哪一個實現。除了支持Dubbo自身的實現以外,還需要提供對外部實現的支持,這樣才能確保整個框架的靈活性。`@Adaptive` 就是爲了解決這個問題而設計的(顯然,一個接口不能有多個@Adaptive實現)。 1、一種情況是對某個實現進行適配。
@Adaptive
註解可以可以用來標註在某個接口的實現類上,表示這個實現並不是用來做業務邏輯處理的,而是用來適配這個接口的各種實現的。
比如:AdaptiveExtensionFactory
被標註了@Adaptive
,在調用ExtensionLoader
的
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
方法時,將返回
AdaptiveExtensionFactory
實例,它用來適配SpiExtensionFactory
和SpringExtensionFactory
實現,具體用哪個,會根據運行時的狀態來確定。
@Adaptive
註解同樣可以標註在接口的方法上。ExtensionLoader通過分析接口配置的adaptvie規則來動態生成adaptvie類。
@Adaptive
有一個value屬性,通過這個值來設置規則。由於Dubbo採取了URL總線的方式來傳遞數據,所以加上該註解的方法參數,需要可以獲得URL
對象,或者直接就是URL
類型或者子類型的對象。以value的值爲key,去URL
裏面查找。
如果查找不到,則會把類名拆分,例如:com.alibaba.dubbo.xxx.YyyInvokerWrapper將會拆分成String[] {“yyy.invoker.wrapper”},用於在URL中查找配置。
如果不能獲取到,則會返回SPI
標註的接口中聲明的名稱的擴展。
@Adaptive接口標註的實現,是一個適配器
2、另一種情況是Dubbo框架動態生成適配器類
如果沒有找到@SPI接口的@Adaptive實現,ExtensionLoader會動態創建適配器類。
ExtensionLoader通過分析接口配置的adaptive規則,動態生成類,並且加載到ClassLoader中。
該註解有一個value屬性,通過這個屬性,可以設置該接口的adaptive規則,所以結合URL總線設計,參數都需要從URL中獲取。
所以這樣的方法都需要提供URL對象作爲參數。
以Transporter類爲例:
@SPI("netty")
public interface Transporter {
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
Server bind(URL url, ChannelHandler handler) throws RemotingException;
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
這個接口是一個擴展接口,默認擴展名爲`netty`。它有兩個方法,
一個是bind,提供URL參數,註解說明,它是配置服務端的transporter功能;
一個是connect,提供URL參數,註解說明,它是配置客戶端的transporter功能;
它沒有Adaptive實現,但是它的方法提供了URL參數。
![](http://owu0nfc62.bkt.clouddn.com/transporter.png)
`ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();`
的時候,按如下調用鏈 `getAdaptiveExtension` -> `createAdaptiveExtension` -> `getAdaptiveExtensionClass` -> `createAdaptiveExtensionClass` -> `createAdaptiveExtensionClassCode`
`createAdaptiveExtensionClassCode`方法,將會構造一個臨時的Adaptive類文件(當然,它需要實現擴展接口,例如Transporter),名字爲`接口名+$Adaptive`
關於接口方法的實現,它將從URL中獲取接口方法註解上的參數,Transporter接口中,如:Constants.SERVER_KEY, Constants.TRANSPORTER_KEY。
用於構造Transporter實現,並且最終生成如下的代碼:
package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adpative implements com.alibaba.dubbo.remoting.Transporter {
public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.common.URL {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.connect(arg0, arg1);
}
public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.common.URL {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.bind(arg0, arg1);
}
}
這樣一來,除了參數獲取方式不一樣。其實和自己定義的Adaptive實現沒什麼區別了。
**拿到Adaptive(其實這就是一個代理工廠),並在代理工廠內屏蔽實現的差異(獲取到準確的擴展實現,然後調用方法,最後返回)**
擴展靈活配置,還體現在另一個註解上`@Activate`。這個註解使用在接口的實現類上,用來標註使用這個實現的前提條件。
比如:`ValidationFilter`被標註爲:
@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.VALIDATION_KEY, order = 10000)
public class ValidationFilter implements Filter {...}
表明這個Filter實現,在做Validation的時候,可以在客戶端和服務端生效;
value表示另外一個激活條件,注意上面截圖的方法,active extension相關的方法,需要提供一個URL參數,
那麼這個value配置的值,表示URL中必須要有指定的參數才能激活這個擴展。
order值越大越靠前。
`這裏指的是對 框架本身對接口的實現 的排序,用戶擴展的實現,將放置在後面。`
Dubbo中對該註解用得最多的地方是`Filter`的實現。Dubbo的調用經常會使用過濾器鏈的形式,哪些實現以及實現的順序,是由`@Activate`註解來控制的。
- 自定義擴展的例子
目標:引入Dubbo框架,自定義擴展,用ExtensionLoader檢驗Dubbo框架是否能正確解析配置,並且返回正確結果。
項目結構搭建省略。
1、定義擴展接口:
@SPI("default")
public interface MyExtension {
@Adaptive
String sayHello(String name, String extensionType);
}
它的方法沒有提供URL,所以,只能顯式定義一個Adaptive類。
2、定義兩個實現:
public class DefaultExtension implements MyExtension {
@Override
public String sayHello(String name, String extensionType) {
return "This is DEFAULT implementation, and hello - "+name;
}
}
public class SWExtension implements MyExtension {
@Override
public String sayHello(String name, String extensionType) {
return "This is SW implementation, and hello - "+name;
}
}
3、定義Adaptive類,根據類型獲取實現。
@Adaptive
public class AdaptiveExtension implements MyExtension {
public String sayHello(String name, String extensionType) {
ExtensionLoader<MyExtension> loader = ExtensionLoader.getExtensionLoader(MyExtension.class);
MyExtension extension = loader.getDefaultExtension();
switch (extensionType){
case "default":
extension = loader.getExtension("default");
break;
case "sw":
extension = loader.getExtension("sw");
break;
default:
break;
}
return extension.sayHello(name,extensionType);
}
}
4、配置META-INF/dubbo/cn.irving.extension.MyExtension
default=cn.irving.extension.DefaultExtension
sw=cn.irving.extension.SWExtension
adaptive=cn.irving.extension.AdaptiveExtension
5、定義測試類
public class ExtensionTest {
public static void main(String[] args) {
MyExtension extension = ExtensionLoader.getExtensionLoader(MyExtension.class).getAdaptiveExtension();
System.out.println(extension.sayHello("Irving","sw"));
// ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
}
}
運行結果:
This is SW implementation, and hello - Irving
參考資料:
http://dubbo.io/books/dubbo-dev-book/SPI.html
https://my.oschina.net/bieber/blog/418949
-EOF-