當使用Dubbo時候,使用@Reference
或者 ReferenceConfig.get
時候獲取的一個目標接口,那麼進行調用時候,接口並不是直接調用到了接口的實現(Impl)類。
public static void main(String[] args) {
ReferenceConfig<HelloService> reference = new ReferenceConfig<>();
reference.setApplication(new ApplicationConfig("dubbo-consumer"));
reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
reference.setInterface(HelloService.class);
HelloService service = reference.get();
String message = service.hello("dubbo I am anla7856");
System.out.println(message);
}
當執行完reference.get()
後,返回的 HelloService
是什麼樣子呢?肯定不是Consumer 端產生的 HelloServiceImpl
,如果是這樣,那過濾器豈不是沒用了?
下面一步一步看博主分析。
ReferenceConfig 的 createProxy
主要就是在 ReferenceConfig
的 createProxy
方法,會產生一個代理對象。
在方法內部檢查完參數之後,則會初始化invoker,如果有指定多個url,則會加入集羣容錯機制,這裏先分析通過Protocol
而獲取 的refer方法而獲取具體invoker對象。
if (urls.size() == 1) {
// 當 url有多個
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// use RegistryAwareCluster only when register's CLUSTER is available
URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
// The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
invoker = CLUSTER.join(new StaticDirectory(u, invokers));
} else { // not a registry url, must be direct invoke.
invoker = CLUSTER.join(new StaticDirectory(invokers));
}
}
而此時,url爲 registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-consumer&dubbo=2.0.2&pid=6501&refer=application%3Ddubbo-consumer%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.anla.rpc.pureapi.service.HelloService%26lazy%3Dfalse%26methods%3Dhello%26pid%3D6501%26register.ip%3D192.168.1.107%26release%3D2.7.2%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1566912707974®istry=zookeeper&release=2.7.2×tamp=1566912927481
此時 REF_PROTOCOL
爲 Protocol$Adaptive
,上一篇文章有講默認的 Adaptive類原理,可以參考:Dubbo進階(七)- Dubbo 中默認的 Adaptive類生成過程及例子。
所以此時加載的將是 RegistryProtocol
,並且執行它的 refer方法。
下面給出 Protocol$Adaptive
類的refer方法
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
RegistryProtocol
上一小節發現,由於url 協議爲 registry,所以當它調用 ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
將傳入 registry
。
毫無疑問是返回一個 RegistryProtocol
,那麼 RegistryProtocol是怎樣的呢?
ExtensionLoader的 getExtension
首先看看 ExtensionLoader
的 getExtension
方法,它是如何組裝並返回這個類的呢?
public T getExtension(String name) {
// 檢查參數
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
// 嘗試從緩存中獲取 實例
Holder<Object> holder = getOrCreateHolder(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;
}
上面代碼主要有以下邏輯:
- 檢查參數
- 檢查是否已有緩存
- 沒有則調用
createExtension(name)
創建一個對應實例
createExtension
在 createExtension
中,就是完成了具體實例的組裝過程:
private T createExtension(String name) {
// 獲取對應的擴展
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
// 嘗試看是否有緩存起來
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 注入相關extension註解
injectExtension(instance);
// 獲取該SPI 裏面所有的包裝類
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
// 由 SPI 文件中定義順序,遍歷
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
這段代碼邏輯比較有意思,除了前面的檢查參數、讀取緩存外,就是使用到了SPI 文件中的包裝類 cachedWrapperClasses
。
而Protocol的SPI 文件名爲:org.apache.dubbo.rpc.Protocol
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol
native-thrift=org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocol
memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
xmlrpc=org.apache.dubbo.xml.rpc.protocol.xmlrpc.XmlRpcProtocol
registry=org.apache.dubbo.registry.integration.RegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
所以wrapperClasses
包裝類集合順序爲ProtocolFilterWrapper
->ProtocolListenerWrapper
->QosProtocolWrapper
。
Dubbo 中 包裝類的特性就是擁有一個 SPI 對應類型的 單參數構造方法。
這樣設計的結構很明顯是可以組裝成爲鏈式調用。
經過遍歷組裝後,得到的鏈式結構爲:
ProtocolFilterWrapper
->ProtocolListenerWrapper
->QosProtocolWrapper
->RegistryProtocol
。
當然上面鏈式結構也不一定準確,但是`RgistryProtocol一定是在最後調用。
開始比較糾結的點是,既然按照順序讀取,爲啥不用List或者其他具有順序性特性的集合呢?
這樣做沒有意義,因爲 classloader 加載的順序也是未知的。應當確保 wrapper class 本身的實現與順序無關。
即保證在 任何一個 Protocol
時候,都會先調用 多個Wrapper
類。
https://github.com/apache/dubbo/issues/4578
RegistryProtocol 的 refer
所以這個refer調用流程,則會按照如下流程調用:
ProtocolFilterWrapper
的refer
方法,目的是初始化以及調用FilterQosProtocolWrapper
的refer
方法,目的是初始化QoS
功能,默認是開啓的ProtocolListenerWrapper
的refer
方法,初始化listener
功能RegistryProtocol
的refer
方法,初始化相關注冊信息,以及註冊監聽器
所以劃重點: classloader加載SPI 文件類中無需性,所以每個類之前都會調用相應的 Wrapper
類,最後纔會調用相關SPI 類。
Protocol
的 SPI 文件中的這三個 Wrapper,當然有不同的功能,但是他們的 refer
方法則都會考慮 是否爲registry
服務。
ProtocolFilterWrapper
的refer
方法:
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
}
QosProtocolWrapper
的refer
方法:
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
startQosServer(url);
return protocol.refer(type, url);
}
return protocol.refer(type, url);
}
RegistryProtocol
的refer
方法:
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
Collections.unmodifiableList(
ExtensionLoader.getExtensionLoader(InvokerListener.class)
.getActivateExtension(url, INVOKER_LISTENER_KEY)));
}
如果是,則會直接去先初始化 RegistryProtocol
,相關注冊中心初始化的且看下回分解。
覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,Dubbo小吃街不迷路: