消費端的代碼解析是從下面這段代碼開始的
- <dubbo:reference id="xxxService" interface="xxx.xxx.Service"/>
- ReferenceBean(afterPropertiesSet) ->getObject() ->get()->init()->createProxy 最終會獲得一個代理對象。
createProxy第375行
- 前面很多代碼都是初始化的動作,需要仔細分析的代碼代碼從createProxy第375行開始
List<URL> us = loadRegistries(false); //從註冊中心上獲得相應的協議url地址
if (us != null && us.size() > 0) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls == null || urls.size() == 0) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
if (urls.size() == 1) {
invoker = refprotocol.refer(interfaceClass, urls.get(0)); //獲得invoker代理對象
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // 用了最後一個registry url
}
}
if (registryURL != null) { // 有 註冊中心協議的URL
// 對有註冊中心的Cluster 只用 AvailableCluster
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // 不是 註冊中心的URL
invoker = cluster.join(new StaticDirectory(invokers));
}
}
refprotocol.refer
- refprotocol這個對象,定義的代碼如下,是一個自適應擴展點,得到的是Protocol$Adaptive。
- Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
- 直接找到Protocol$Adaptive代碼中的refer代碼塊如下
- 這段代碼中,根據當前的協議url,得到一個指定的擴展點,傳遞進來的參數中,協議地址爲registry://,
- 所以,我們可以直接定位到RegistryProtocol.refer代碼
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);
}
RegistryProtocol.refer
- 這個方法裏面的代碼,基本上都能看懂
- 根據根據url獲得註冊中心,這個registry是zookeeperRegistry
- 調用doRefer,按方法,傳遞了幾個參數, 其中有一個culster參數,這個需要注意下
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0 ) {
if ( ( Constants.COMMA_SPLIT_PATTERN.split( group ) ).length > 1
|| "*".equals( group ) ) {
return doRefer( getMergeableCluster(), registry, type, url );
}
}
return doRefer(cluster, registry, type, url);
}
cluster
- doRefer方法中有一個參數是cluster,我們找到它的定義代碼如下,
- 又是一個自動注入的擴展點。
- setter 方法在injectExtention 中注入
private Cluster cluster;
public void setCluster(Cluster cluster) {
this.cluster = cluster;
}
- 從下面的代碼可以看出,這個不僅僅是一個擴展點,而且方法層面上,還有一個@Adaptive,表示會動態生成一個自適應適配器
Cluster$Adaptive
@SPI(FailoverCluster.NAME)
public interface Cluster {
/**
* Merge the directory invokers to a virtual invoker.
*
* @param <T>
* @param directory
* @return cluster invoker
* @throws RpcException
*/
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
Cluster$Adaptive
- refprotocol這個對象,定義的代碼如下,是一個自適應擴展點,得到的是Protocol$Adaptive。
- Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
- 直接找到Protocol$Adaptive代碼中的refer代碼塊如下
- 這段代碼中,根據當前的協議url,得到一個指定的擴展點,傳遞進來的參數中,協議地址爲registry://,
- 所以,我們可以直接定位到RegistryProtocol.refer代碼
public class Cluster$Adaptive implements com.alibaba.dubbo.rpc.cluster.Cluster {
public com.alibaba.dubbo.rpc.Invoker join(com.alibaba.dubbo.rpc.cluster.Directory arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("cluster", "failover");
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.cluster.Cluster) name from url(" + url.toString() + ") use keys([cluster])");
com.alibaba.dubbo.rpc.cluster.Cluster extension = (com.alibaba.dubbo.rpc.cluster.Cluster) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
return extension.join(arg0);
}
}
RegistryProtocol.doRefer
這段代碼中,有一個RegistryDirectory,可能看不懂,我們暫時先忽略,等會單獨講.(基於註冊中心動態發現服務提供者)
- 將consumer://協議地址註冊到註冊中心
- 訂閱zookeeper地址的變化
- 調用cluster.join()方法
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
return cluster.join(directory);
}
cluster.join
- 由前面的Cluster$Adaptive這個類中的join方法的分析,得知cluster.join會調用MockClusterWrapper.join方法,
- 然後再調用FailoverCluster.join方法。
-
這個意思很明顯了。也就是我們上節課講過的mock容錯機制,如果出現異常情況,會調用MockClusterInvoker,否則,調用FailoverClusterInvoker.
public class MockClusterWrapper implements Cluster {
private Cluster cluster;
public MockClusterWrapper(Cluster cluster) {
this.cluster = cluster;
}
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new MockClusterInvoker<T>(directory,
this.cluster.join(directory));
}
}
小結
- refprotocol.ref,這個方法,會返回一個MockClusterInvoker(FailoverClusterInvoker)。
- 這裏面一定還有疑問,我們先把主線走完,再回過頭看看什麼是cluster、什麼是directory
proxyFactory.getProxy(invoker);
- 再回到ReferenceConfig這個類,在createProxy方法的最後一行,調用proxyFactory.getProxy(invoker). 把前面生成的invoker對象作爲參數,再通過proxyFactory工廠去獲得一個代理對象。接下來我們分析下這段代碼做了什麼。
- 其實前面在分析服務發佈的時候,基本分析過了,所以再看這段代碼,應該會很熟悉
- ProxyFactory, 會生成一個動態的自適應適配器。ProxyFactory$Adaptive,然後調用這個適配器中的getProxy方法,代碼如下
public java.lang.Object getProxy(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.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
- 很顯然,又是通過javassist實現的一個動態代理,我們來看看JavassistProxyFactory.getProxy
JavassistProxyFactory.getProxy
- 通過javasssist動態字節碼生成動態代理類,
proxy.getProxy(interfaces)
- 在Proxy.getProxy這個類的如下代碼中添加斷點,在debug下可以看到動態字節碼如下
public java.lang.String sayHello(java.lang.String arg0){
Object[] args = new Object[1];
args[0] = ($w)$1;
Object ret = handler.invoke(this, methods[0], args);
return (java.lang.String)ret;
}
- 上面紅色部分代碼的handler,就是在JavassistProxyFactory.getProxy中。
- 傳遞的new InvokerInvocationHandler(invoker)看到這裏,
什麼時候建立和服務端的連接
- 前面我們通過代碼分析到了,消費端的初始化過程,但是似乎沒有看到客戶端和服務端建立NIO連接。
- 實際上,建立連接的過程在消費端初始化的時候就建立好的,只是前面我們沒有分析,
- 代碼在RegistryProtocol.doRefer方法內的directory.subscribe方法中。
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
return cluster.join(directory);
}
directory.subscribe
調用鏈爲: RegistryDirectory.subscribe ->FailbackRegistry. subscribe->- AbstractRegistry.subscribe>zookeeperRegistry.doSubscribe
public void subscribe(URL url) {
setConsumerUrl(url);
registry.subscribe(url, this);
}
FailbackRegistry. subscribe
- 調用FailbackRegistry.subscribe 進行訂閱,這裏有一個特殊處理,如果訂閱失敗,則會添加到定時任務中進行重試
@Override
public void subscribe(URL url, NotifyListener listener) {
super.subscribe(url, listener);
removeFailedSubscribed(url, listener);
try {
// 向服務器端發送訂閱請求
doSubscribe(url, listener);
zookeeperRegistry. doSubscribe
調用zookeeperRegistry執行真正的訂閱操作,這段代碼太長,我就不貼出來了,這裏面主要做兩個操作
- 對providers/routers/configurator三個節點進行創建和監聽
- 調用notify(url,listener,urls) 將已經可用的列表進行通知
AbstractRegistry.notify
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
if ((urls == null || urls.size() == 0)
&& ! Constants.ANY_VALUE.equals(url.getServiceInterface())) {
logger.warn("Ignore empty notify urls for subscribe url " + url);
return;
}
if (logger.isInfoEnabled()) {
logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
}
Map<String, List<URL>> result = new HashMap<String, List<URL>>();
for (URL u : urls) {
if (UrlUtils.isMatch(url, u)) {
String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
List<URL> categoryList = result.get(category);
if (categoryList == null) {
categoryList = new ArrayList<URL>();
result.put(category, categoryList);
}
categoryList.add(u);
}
}
if (result.size() == 0) {
return;
}
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified == null) {
notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
categoryNotified = notified.get(url);
}
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
String category = entry.getKey();
List<URL> categoryList = entry.getValue();
categoryNotified.put(category, categoryList);
saveProperties(url);
listener.notify(categoryList);
}
}
總結
消費端初始化這塊就完了,