上圖是服務提供者暴露服務的主過程html
首先ServiceConfig類拿到對外提供服務的實際類ref(如:HelloServiceImpl),而後經過ProxyFactory類的getInvoker方法使用ref生成一個AbstractProxyInvoker實例,到這一步就完成具體服務到Invoker的轉化。接下來就是Invoker轉換到Exporter的過程。java
Dubbo處理服務暴露的關鍵就在Invoker轉換到Exporter的過程(如上圖中的紅色部分),Dubbo協議的Invoker轉爲Exporter發生在DubboProtocol類的export方法,它主要是打開socket偵聽服務,並接收客戶端發來的各類請求,通信細節由Dubbo本身實現.apache
一、獲取註冊中心信息,構建協議信息,而後將其組合。
二、經過ProxyFactory將HelloServiceImpl封裝成一個Invoker執行 。
三、使用Protocol將invoker導出成一個Exporter(包括去註冊中心註冊服務等)。緩存
這裏面就涉及到幾個大的概念,ProxyFactory、Invoker、Protocol、Exporter服務器
一、首先會檢查各類配置信息<dubbo:service/>、<dubbo:registry/>、<dubbo:protocol/>
等標籤的配置,填充各類屬性,總之就是保證我在開始暴露服務以前,全部的東西都準備好了,而且是正確的。
二、加載全部的註冊中心,由於咱們暴露服務須要註冊到註冊中心中去。
三、根據配置的全部協議和註冊中心url分別進行導出。
四、進行導出的時候,又是一波屬性的獲取設置檢查等操做。
五、若是配置的不是remote,則作本地導出。
六、若是配置的不是local,則暴露爲遠程服務。
七、不論是本地仍是遠程服務暴露,首先都會獲取Invoker。
八、獲取完Invoker以後,轉換成對外的Exporter,緩存起來。
九、執行DubboProtocol類的export方法,打開socket偵聽服務,並接收客戶端發來的各類請求。app
先看一個簡單的服務端例子,dubbo配置以下:負載均衡
<dubbo:application name="helloService-app" /> <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" /> <dubbo:service interface="com.demo.dubbo.service.HelloService" ref="helloService" /> <dubbo:protocol name="dubbo" port="20880" /> <bean id="helloService" class="com.demo.dubbo.server.serviceimpl.HelloServiceImpl"/>
Invoker,一個可執行對象,可以根據方法名稱、參數獲得相應的執行結果。接口以下:socket
public interface Invoker<T> { Class<T> getInterface(); URL getUrl(); Result invoke(Invocation invocation) throws RpcException; void destroy(); }
而Invocation則包含了須要執行的方法、參數等信息,接口以下:ide
public interface Invocation { URL getUrl(); String getMethodName(); Class<?>[] getParameterTypes(); Object[] getArguments(); }
目前其實現類只有一個RpcInvocationurl
Invoker這個可執行對象的執行過程分紅三種類型:
以HelloService接口爲例:
看下Invoker的實現狀況:
對於Server端,主要負責將服務如HelloServiceImpl統一進行包裝成一個Invoker,經過反射來執行具體的HelloServiceImpl對象的方法
接口定義以下:
@SPI("javassist") public interface ProxyFactory { //針對client端,建立出代理對象 @Adaptive({Constants.PROXY_KEY}) <T> T getProxy(Invoker<T> invoker) throws RpcException; //針對server端,將服務對象如HelloServiceImpl包裝成一個Invoker對象 @Adaptive({Constants.PROXY_KEY}) <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException; }
ProxyFactory的接口實現有JdkProxyFactory、JavassistProxyFactory,默認是JavassistProxyFactory, JavassistProxyFactory內容以下:
public class JavassistProxyFactory extends AbstractProxyFactory { @SuppressWarnings("unchecked") public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) { return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); } public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // TODO Wrapper類不能正確處理帶$的類名 final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); return new AbstractProxyInvoker<T>(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable { return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; } }
能夠看到建立了一個AbstractProxyInvoker(這類就是本地執行的Invoker),AbstractProxyInvoker對invoke()方法的實現以下:
public Result invoke(Invocation invocation) throws RpcException { try { return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments())); } catch (InvocationTargetException e) { return new RpcResult(e.getTargetException()); } catch (Throwable e) { throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e); } }
綜上所述,服務發佈的第二個過程就是:使用ProxyFactory將HelloServiceImpl封裝成一個本地執行的Invoker。
從上面得知服務發佈的第1、二個過程:
一、獲取註冊中心信息dubbo:registry和協議信息dubbo:protocol。
二、使用ProxyFactory將HelloServiceImpl封裝成一個本地執行的Invoker。
執行這個服務->執行這個本地的Invoker->調用AbstractProxyInvoker.invoke(Invocation invocation)方法,方法的執行過程就是經過反射執行HelloServiceImpl。
如今的問題是:客戶端如何調用服務端的方法(服務註冊到註冊中心->客戶端向註冊中心訂閱服務->客戶端調用服務端的方法)和上述Invocation參數的來源問題。
對於Server端來講,上述服務發佈的第3步中Protocol要解決的問題是:
先來看下Protocol接口的定義:
@SPI("dubbo") public interface Protocol { int getDefaultPort(); //針對server端來講,將本地執行類的Invoker經過協議暴漏給外部。這樣外部就能夠經過協議發送執行參數Invocation,而後交給本地Invoker來執行 @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; //這個是針對客戶端的,客戶端從註冊中心獲取服務器端發佈的服務信息 //經過服務信息得知服務器端使用的協議,而後客戶端仍然使用該協議構造一個Invoker。這個Invoker是遠程通訊類的Invoker。 //執行時,須要將執行信息經過指定協議發送給服務器端,服務器端接收到參數Invocation,而後交給服務器端的本地Invoker來執行 @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; void destroy(); }
咱們再來詳細看看服務發佈的第3步(ServiceConfig):
Exporter<?> exporter = protocol.export(invoker);
protocol的來歷是:
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
public com.alibaba.dubbo.rpc.Exporter export(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.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)com.alibaba.dubbo.common.ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } 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)com.alibaba.dubbo.common.ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); }
export(Invoker invoker)的過程即根據Invoker中url的信息來最終選擇Protocol的實現,默認實現是DubboProtocol,而後再對DubboProtocol進行依賴注入,進行wrap包裝(getExtension()方法)。
Protocol的實現狀況:
能夠看到在返回DubboProtocol以前,通過了ProtocolFilterWrapper、ProtocolListenerWrapper、RegistryProtocol的包裝
所謂包裝就是以下內容:
package com.alibaba.xxx; import com.alibaba.dubbo.rpc.Protocol; public class XxxProtocolWrapper implemenets Protocol { Protocol impl; public XxxProtocol(Protocol protocol) { impl = protocol; } // 接口方法作一個操做後,再調用extension的方法 public Exporter<T> export(final Invoker<T> invoker) { //... 一些操做 impl .export(invoker); // ... 一些操做 } // ... }
使用裝飾器模式,相似AOP的功能
下面主要講解RegistryProtocol和DubboProtocol,先暫時忽略ProtocolFilterWrapper、ProtocolListenerWrapper
RegistryProtocol.export() 主要功能是將服務註冊到註冊中心
DubboProtocol.export() 服務導出功能:
如今咱們搞清楚咱們的目的,經過通訊對象獲取客戶端傳來的Invocation參數,而後找到對應的DubboExporter(即可以獲取到本地Invoker)就能夠執行服務了。
在DubboProtocol中,每一個ExchangeServer通訊對象都綁定了一個ExchangeHandler對象,內容以下:
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() { public Object reply(ExchangeChannel channel, Object message) throws RemotingException { if (message instanceof Invocation) { Invocation inv = (Invocation) message; Invoker<?> invoker = getInvoker(channel, inv); RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress()); ...... return invoker.invoke(inv); } throw new RemotingException(channel, "Unsupported request: " + message == null ? null : (message.getClass().getName() + ": " + message) + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress()); } };
能夠看到該類纔是真正與客戶端通訊,在獲取到Invocation參數後,調用getInvoker()來獲取本地的Invoker(先從exporterMap中獲取Exporter),就能夠調用服務了。
而對於通訊這塊,接下來會專門來詳細的說明,從reply參數可知,重點在瞭解ExchangeChannel。
負責維護invoker的生命週期,包含一個Invoker對象,接口定義以下:
public interface Exporter<T> { Invoker<T> getInvoker(); void unexport(); }
以上就是本文簡略地介紹了及服務發佈過程當中的幾個 ProxyFactory、Invoker、Protocol、Exporter 概念
參考:http://dubbo.apache.org/books/dubbo-dev-book/implementation.html
參考:https://blog.csdn.net/qq418517226/article/details/51818769