今天打算來說一講 Dubbo 服務遠程調用。筆者在開始看 Dubbo 遠程服務相關源碼的時候,看的有點迷糊。後來慢慢明白 Dubbo 遠程服務的調用的本質就是動態代理模式的一種實現。本地消費者無須知道遠程服務具體的實現,消費者和提供者經過代理類來進行交互!!java
簡單看一段代碼回顧一下動態代理:apache
public class MyInvocationHandler implements InvocationHandler{
private Object object;
public MyInvocationHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object, args);
return result;
}
}
public static void main(String[] args) {
MyInvocationHandler handler = new MyInvocationHandler(stu);
// 生成代理類
Student proxy = (Student)Proxy.newProxyInstance(loader, interfaces, handler);
// 經過代理類調用 sayHello 方法
proxy.sayHello(message);
}
複製代碼
實現動態代理的核心步驟有兩步:
一、自定義實現了 InvocationHandler 接口的 handler 類,並重寫 invoke() 方法
二、調用 Proxy.newProxyInstance 方法建立代理類bootstrap
咱們最後在調用 proxy.sayHello() 的時候,代理類會調用 MyInvocationHandler 類的 invoke() 方法,invoke() 方法經過反射機制最終調用 sayHello() 方法。既然對動態代理的核心組成已經瞭然,接下來咱們就結合這兩點分析下 Dubbo 遠程服務調用的實現。windows
在 【Dubbo源碼閱讀系列】之 Dubbo XML 配置加載 中咱們分析了 Dubbo 解析 XML 配置文件的相關流程,其中 <dubbo:service /> 標籤會被解析爲 ServiceBean 對象。相似的,<dubbo:reference /> 標籤會被解析爲 ReferenceBean 對象。ReferenceBean 類繼承自 ReferenceConfig 類,仔細觀察 ReferenceConfig 類不難發現 ReferenceConfig 中存在這樣一條調用鏈。
get() ==> init() ==> createProxy()
不要懷疑...crateProxy() 方法就是咱們今天的主角...更使人激動的是,咱們能夠在該方法中找到與前文概括的動態代理實現核心步驟相對應的代碼實現:緩存
invoker = refprotocol.refer(interfaceClass, urls.get(0));
複製代碼
這裏返回的對象爲 Invoker 對象。Invoke 類是一個接口類,裏面定義了一個 invoke() 方法。return (T) proxyFactory.getProxy(invoker);
複製代碼
在這一小節,咱們只須要對代理類建立流程有個大體的印象便可,咱們在後文深刻分析具體流程。bash
invoker = refprotocol.refer(interfaceClass, urls.get(0));
複製代碼
熟悉的配方熟悉的料,經過 Dubbo SPI 機制咱們發現這裏調用的實際爲 RegistryProtocol.refer(),問我爲啥?詳見:【Dubbo源碼閱讀系列】之 Dubbo SPI 機制
refprotocol.refer() 流程比較長,先放張時序圖讓你們有個基本的印象:app
這裏簡單歸納下上圖中的重點內容:jvm
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
複製代碼
這裏會新建一個 DubboInvoker 對象並返回,咱們會在後文詳細分析;在上節中關於 invoker 的建立咱們留了個小尾巴沒有講完。代碼以下:ide
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
複製代碼
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
複製代碼
總體流程比較簡單,可是注意看,這裏有個很重要的方法:getClient(url)。它是用來幹啥的?還記得咱們再服務暴露之遠程暴露那一節啓動了 Netty 服務端嗎?當時留了個關於 Netty 客戶端在哪裏啓動的坑。這裏的 getClients() 就是用來開啓 Netty 客戶端的。return getExchanger(url).connect(url, handler);
複製代碼
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}
複製代碼
核心方法爲 Transporters.connect()return getTransporter().connect(url, handler);
複製代碼
public Client connect(URL url, ChannelHandler listener) throws RemotingException {
return new NettyClient(url, listener);
}
複製代碼
NettyClient 類構造方法會調用父類 AbstractClient 構造方法。核心方法有兩個:
終於要開始建立代理類了,回顧下 ReferenceConfig 中 createProxy() 方法最後一句:post
return (T) proxyFactory.getProxy(invoker);
複製代碼
Invoker 對象的建立已經在第二小節詳細分析過了。那麼 proxyFactory 的 getProxy() 到底幹了什麼呢?實際上這裏又藉助了 Dubbo SPI 機制的實現。執行流程大體爲:
proxyFactory.getProxy(invoker) ==》 StubProxyFactoryWrapper.getProxy(invoker) ==》AbstractProxyFactory.getProxy(invoker) ==》AbstractProxyFactory.getProxy(invoker, generic) ==> JavassistProxyFactory.getProxy(invoker, interfaces)
重點看 JavassistProxyFactory.getProxy() 方法:
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
複製代碼
這裏有兩點值得提一下:
package org.apache.dubbo.common.bytecode;
import com.alibaba.dubbo.rpc.service.EchoService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.apache.dubbo.common.bytecode.ClassGenerator.DC;
import org.apache.dubbo.demo.DemoService;
public class proxy0 implements DC, EchoService, DemoService {
public static Method[] methods;
private InvocationHandler handler;
public proxy0(InvocationHandler var1) {
this.handler = var1;
}
public proxy0() {
}
public String sayHello(String var1) {
Object[] var2 = new Object[]{var1};
Object var3 = this.handler.invoke(this, methods[0], var2);
return (String)var3;
}
public Object $echo(Object var1) {
Object[] var2 = new Object[]{var1};
Object var3 = this.handler.invoke(this, methods[1], var2);
return (Object)var3;
}
}
複製代碼
最後囉嗦一下如何在 windows 系統下查看使用 javassist 字節碼技術生成的代理類!!
小結:這一小節寫的比較水~不過不要緊,意思已經到了!!