今天要聊一個可能被其他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