Dubbo 攔截器和監聽器(轉)
今天要聊一個可能被其他dubbo原始碼研究的童鞋容易忽略的話題:Filter和Listener。
我們先來看一下這兩個概念的官方手冊:
· 攔截器
老實說,依賴之前的原始碼分析經驗,導致我饒了很大的彎路,一直找不到filter和listener被使用的位置。看過前幾篇文章的朋友應該也有這個疑惑,為什麼按照url引數去匹配框架的執行流程,死活找不到dubbo注入攔截器和監聽器的位置呢?
ReferenceConfig --> RegistryProtocol --> DubboProtocol --> invoker --> exporter
按照這個呼叫流程,沒錯啊,可每一個環節都沒有使用filter和listener屬性的痕跡,有點抓瞎了啊。要說用好IDE確實很重要啊,光靠腦子想真的很傷身,下面來看一下謎底。
先來回憶一下dubbo的SPI機制,根據介面型別,dubbo會去讀取並解析對應的配置檔案,從中拿到對應的擴充套件點實現,好,我們先來看一下Protocol介面對應的配置檔案:
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper #注意這一行
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper #注意這一行
mock=com.alibaba.dubbo.rpc.support.MockProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol
我們已經找到了filter和listener對應的擴充套件點了。接下來看一下它們是怎麼一步一步的被注入到上面的流程裡的。
在ReferenceConfig類中我們會引用和暴露對應的服務,我們以服務引用為場景來分析:
get() --> init() --> createProxy()
|
+---> invoker = refprotocol.refer(interfaceClass, urls.get(0));
注意上面提到的這一行程式碼,這裡的refprotocol是引用的Protocol$Adpative,這個類是dubbo的SPI機制動態建立的自適應擴充套件點,我們在之前的文章中已經介紹過,看一下它的refer方法細節:
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
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])");
//注意這一行,根據url的協議名稱選擇對應的擴充套件點實現
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);
}
乍一看,並沒有感覺有什麼蹊蹺,不過在單步除錯中就會出現”詭異”現象(由於該類是動態建立的,所以該方法並不會被單步到,所以為分析帶來了一定的干擾),我們得再往回倒一下,之前在dubbo中SPI的基礎中曾經分析過ExtensionLoader的原始碼,但是當時由於瞭解的不夠確實忽略了一些細節。
我們再來看一下它的執行流程:
getExtension() --> createExtension()
|
+--> ......
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class<?> wrapperClass : wrapperClasses) { //裝飾器模式
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
......
一看到這行程式碼,就知道關鍵點在這裡,這種寫法剛好就是和常見的攔截器和監聽器的實現方法吻合,而且事實證明也確實是在這個地方完成的注入,那麼我們就需要看一下這個cachedWrapperClasses到到底存了什麼?
我們最後看一下ExtensionLoader.loadFile方法,它是負責解析我們開頭提到的那個SPI擴充套件點配置檔案的,它會依次掃描配置檔案的每一行,然後根據配置內容完成等號兩邊的鍵值對應關係,例如:
test=com.alibaba.dubbo.rpc.filter.TestFilter
loadFile的任務就是把test和解析過以後的TestFilter類關係對應上,供以後的getExtension查詢使用。注意看其中的這幾行程式碼:
......
clazz.getConstructor(type); //判斷是否為wrapper實現Set<Class<?>> wrappers = cachedWrapperClasses;if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
......
這裡就完成了cachedWrapperClasses的初始化,它根據檢視配置檔案中定義的擴充套件點實現是否包含一個帶有當前型別的構造方法為條件,確定哪些是wrapper,這樣我們就可以發現:
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapperlistener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
這兩行命中了。這也是之後在真正獲取protocol擴充套件點時會動態注入的兩個重要包裝類,前者完成攔截器,後者完成監聽器。
轉載自:https://my.oschina.net/oosc/blog/1791188?tdsourcetag=s_pctim_aiomsg