Dubbo服務呼叫過程原始碼解析④
阿新 • • 發佈:2020-12-27
[TOC]
> [Dubbo SPI原始碼解析①](https://www.cnblogs.com/lbhym/p/14192704.html)
>
> [Dubbo服務暴露原始碼解析②](https://www.cnblogs.com/lbhym/p/14192711.html)
>
> [Dubbo服務引用原始碼解析③](https://www.cnblogs.com/lbhym/p/14196767.html)
經過前面三章的分析,瞭解了Dubbo的基礎:Dubbo SPI,瞭解了Provider的服務暴露和Consumer的服務引用。最後我們需要學習一下服務完整的呼叫過程。Dubbo服務呼叫過程雖然複雜,比如包含傳送請求、編解碼、服務降級、過濾器、序列化、執行緒派發以及響應請求等步驟。但是先理解其大概的邏輯過程,再重點看一下主要的幾個類,其實也非常好理解。
分析之前放一張官網的呼叫過程圖:
![](https://img2020.cnblogs.com/blog/1383122/202012/1383122-20201227112155935-1368978810.png)
首先消費者通過代理物件發起請求,通過網路通訊客戶端將編碼後的請求傳送給Provider的Server。Server收到後進行解碼。解碼後的請求交給Dispatcher分發器,再由分發器分配到指定的執行緒池上,最後由執行緒池執行具體的服務。還有回發響應的過程這張圖並沒有體現出來。在正式開始分析之前,最好開啟自己的IDE,一起跟蹤原始碼,看得更清楚。
## 0.服務的呼叫
由上面那個圖可以看到,呼叫源於代理物件Proxy。代理類是動態生成的,直接操作的位元組碼,我們需要把它反編譯一下,看一下它到底長什麼樣。Dubbo用的是Javassist,我們使用也是阿里開源的診斷工具Arthas反編譯看一下。首先去它的官網下載軟體包:https://arthas.aliyun.com/doc/download.html
解壓後,進入到軟體根目錄,執行如下命令啟動:
```
java -jar arthas-boot.jar
```
啟動後,終端上會顯示Java程序列表,比如這樣:(注意這時候需要你啟動消費者,保持執行)。
![](https://img2020.cnblogs.com/blog/1383122/202012/1383122-20201227112245492-1887374294.png)
接著輸入Consumer對應編號,比如4。Arthas就會關聯到這個程序。由於我這個Demo只有一個服務介面,所以生成的代理類也只有一個,我們直接根據字尾名搜尋一下:
```
sc *.proxy0
```
![](https://img2020.cnblogs.com/blog/1383122/202012/1383122-20201227112316750-1084973053.png)
記住這個路徑,最後用jad命令反編譯:
```
jad com.alibaba.dubbo.common.bytecode.proxy0
```
編譯完成後,終端上就會顯示對應的代理類:
```java
public class proxy0 implements ClassGenerator.DC,ServiceAPI,EchoService {
//方法陣列
public static Method[] methods;
private InvocationHandler handler;
public proxy0(InvocationHandler invocationHandler) {
this.handler = invocationHandler;
}
public proxy0() {
}
public String sendMessage(String string) {
//把引數存到Object陣列
Object[] arrobject = new Object[]{string};
//呼叫InvocationHandler的invoke方法
Object object = this.handler.invoke(this, methods[0], arrobject);
return (String)object;
}
//測試方法
@Override
public Object $echo(Object object) {
Object[] arrobject = new Object[]{object};
Object object2 = this.handler.invoke(this, methods[1], arrobject);
return object2;
}
}
```
整個代理類比較簡單,主要就是呼叫了InvocationHandler的invoke方法。我麼找到它的實現類,在Dubbo中,它的實現類是InvokerInvocationHandler:
```java
public class InvokerInvocationHandler implements InvocationHandler {
private final Invoker> invoker;
public InvokerInvocationHandler(Invoker> handler) {
this.invoker = handler;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
}
```
通過除錯我們發現,這個invoker變數的型別是MockClusterInvoker,也就是最後會呼叫這個類的invoke方法。MockClusterInvoker#invoke會呼叫AbstractClusterInvoker#invoke方法,接著執行一些服務降級的邏輯。接下來又是一連串呼叫,我們直接看關鍵方法:DubboInvoker#doInvoke
```java
protected Result doInvoke(final Invocation invocation) throws Throwable {
//它會記錄呼叫方法、介面、引數等資訊
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
//設定path和version到inv的attachments
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
inv.setAttachment(Constants.VERSION_KEY, version);
//獲取通訊客戶端
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
//獲取非同步配置
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
//isOneway為true時,代表單向通訊
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
//非同步無返回值
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
//傳送請求
currentClient.send(inv, isSent);
//設定上下文futrue為null
RpcContext.getContext().setFuture(null);
//返回空的結果
return new RpcResult();
//非同步有返回值
} else if (isAsync) {
//傳送請求,並得到一個future
ResponseFuture future = currentClient.request(inv, timeout);
//把future設定到上下文中
RpcContext.getContext().setFuture(new FutureAdapter