# 發布服務
在Spring的ClassPathXmlApplicationContext初始化結束后,會觸發ApplicationEvent事件;dubbo使用`<dubbo:service>`發布服務,該標簽對應的是ServiceBean,這個類實現了ApplicationListener接口,并在ContextRefreshedEvent事件時調用export()發布服務。

## 1.檢查配置
1.export()中會判斷是否需要發布,如果provider的export為false則不發布;還會判斷是否需要延遲發布,如果需要則加入到線程池中延遲執行,否則立即執行。
2.發布前檢查,如果已經發布或取消發布,則結束;如果未設置interface則拋出異常;從provider中獲取application應用、module模塊、registries注冊中心、monitor監控中心、protocols協議等信息;若模塊不為空,則從模塊中獲取registries注冊中心和monitor監控中心;若application應用不為空,那則從應用中獲取registries注冊中心和monitor監控中心;
3.設置并加載interface指定的class,即檢查class存在并且為接口類型;檢查ref引用的實現類不為空并且是interfaceClass的實現類。
4.如果設置了local和stub,檢查class是否存在;stub在客戶端設置,stub的構造方法引用了遠程接口,用于想在客戶端也執行部分邏輯。
5.檢查application應用、registry注冊中心和protocol協議、server的相關環境配置,
6.調用doExportUrls發布服務的url
## 2.根據注冊中心配置信息生成url
1. 檢查`<dubbo:registry>`,如果未設置,從`dubbo.registry.address`獲取,如果還沒有找到注冊中心的配置則拋出異常。最后,從系統配置中獲取參數修改注冊中心的配置。
2. 遍歷注冊中心,將注冊中心轉為url的形式,一個provider可能有多個注冊中心,一個注冊中心的配置中可能有多個地址;例如:
```
<dubbo:application name="demo-provider"/>
<dubbo:registry address="multicast://224.5.6.7:1234"/> 轉為:
multicast://224.5.6.7:1234/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&pid=17690×tamp=1511357579872
```
最后,修改協議為registry,原先的協議放在參數registry的值中,最后我們獲取了多個注冊中心的url,如:
```
registry://224.5.6.7:1234/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&pid=753®istry=multicast×tamp=1511362038080
```
## 3. 發布服務
遍歷配置的協議,將服務發布到注冊中心,配置文件中可能配置了多個協議,每個服務針對多個協議會生成對應的多個服務url,內部包含了服務信息。
1.生成服務對應協議的url,先獲取ip和port,provider注冊&監聽ip地址,注冊與監聽ip可獨立配置 配置優先級:系統環境變量 -> java命令參數-D -> 配置文件host屬性 -> /etc/hosts中hostname-ip映射關系 -> 默認聯通注冊中心地址的網卡地址 -> 第一個可用的網卡地址;provider注冊&監聽端口,注冊與監聽port可獨立配置 配置優先級:啟動環境變量 -> java命令參數-D -> protocol配置文件port屬性配置 -> 協議默認端口
```
<dubbo:protocol name="dubbo" port="20880"/> DemoService服務生成dubbo協議的url
dubbo://192.168.1.30:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.1.30&bind.port=20880&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=17690&side=provider×tamp=1511357867209
```
2. 查找ConfiguratorFactory的實現類,可以配置修改協議的url
3. scope為none時不暴露服務,scope為remote時,只發布到遠程,為local時,只發布到本地
### 3.1 生成Invoker
Invoker是一個調用器,主要的功能是客戶端請求服務時,服務端會查找發布服務時生成的Invoker方法,根據Invocation對象指定的方法和參數,執行其doInvoke方法,并將結果包裝為Result對象返回給客戶端,從而實現遠程調用。我們先來看一個示例,
```
//1. 創建了一個invoker實例,內部會調用DemoService的實現類的sayHello方法,并講結果包裝為Result對象。
Invoker<DemoService> invoker = new AbstractInvoker(DemoService.class,new URL("DUBBO", "127.0.0.1", 28001)) {
@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
DemoServiceImpl impl = new DemoServiceImpl();
return new RpcResult(impl.sayHello((String)invocation.getArguments()[0]));
}
};
// 2. 調用器根據Invocation指定的方法和參數調用實現類并返回結果
Result result = invoker.invoke(new RpcInvocation("sayHello", new Class[] {String.class}, new Object[] {"World!"}));
System.out.println(result.getValue()); // 輸出為:Hello World!
```
上面的例子中,將DemoService的實現類封裝成為了一個Invoker類,內部可以根據方法名執行不同的方法,為了便于演示,寫死了調用sayHello方法。
在dubbo中,使用下面的方法來創建Invoker實例,具體的代碼在ServiceConfig.java中。
```
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
```
dubbo使用proxyFactory的getInvoker生成Invoker實例。我們先來討論一下proxyFactory,根據ExtensionLoader的實現,proxyFactory獲取Adaptive類時,首先找@Adaptive注解的類,如果沒有會由dubbo創建一個新的類ProxyFactory$Adaptive,其內部會根據url的proxy值獲取factory,默認取proxyFactor接口上的注解`@SPI("javassist")`指定的值,getExtension()時會根據傳入的參數獲取對應的ProxyFactory實現類,還會查找ProxyFactory實現類中將proxyFactory作為唯一參數的構造函數的實現類作為Wrapper類進行包裝。
```
// ProxyFactory$Adaptive類
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1,
com.alibaba.dubbo.common.URL arg2) throws com.alibaba.dubbo.rpc.RpcException {
if (arg2 == null)
throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy", "javassist"); // 默認是javassist
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) 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.getInvoker(arg0, arg1, arg2);
}
```
對ProxyFactory接口來說,最終生成的類是StubProxyFactoryWrapper,內部的proxyFactory為JavassistProxyFactory。proxyFactory變量的對象鏈如下:
```
-| StubProxyFactoryWrapper
-| JavassistProxyFactory
```
StubProxyFactoryWrapper的getInvoker方法會調用內部JavassistProxyFactory的getInvoker
```
// JavassistProxyFactory
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);
}
};
}
```
JavassistProxyFactory內部邏輯,首先使用`Wrapper.getWrapper`將指定的ref類包裝為Wrapper類,Wrapper類內部包含`invokeMethod(Object instance, String mn, Class<?>[] types, Object[] args)`可以調用指定實例的某個方法。最后將Wrapper包裝為一個AbstractProxyInvoker類。Wrapper類動態生成,內部會直接調用實現類的方法,而不是使用反射調用,下面是invokeMethod的實現。
```
public Object invokeMethod(Object o, String n, Class[] p, Object[] v){
com.alibaba.dubbo.demo.provider.DemoServiceImpl w;
try{
w = ((com.alibaba.dubbo.demo.provider.DemoServiceImpl)$1);
}catch(Throwable e){
throw new IllegalArgumentException(e);
}
try{
if( "sayHello".equals( $2 ) && $3.length == 1 ) {
return ($w)w.sayHello((java.lang.String)$4[0]); }
} catch(Throwable e) {
throw new java.lang.reflect.InvocationTargetException(e);
}
}
```
經過這一系列的過程,我們完成了Invoker對象的創建,下面是內部調用結構:
```
|-AbstractProxyInvoker:invoker()
|-Wrapper:invokeMethod()
|-DemoService : 指定的方法
```
### 3.2 發布Invoker
由于不同協議發布的方式不同,因此根據dubbo的理念,會根據url加載不同的protocol實現類來發布AbstractProxyInvoker的實例。與ProxyFactory類似,ExtensionLoader加載Protocol后的protocol實例為:
```
|- ProtocolFilterWrapper
|- ProtocolListenerWrapper
|- RegistryProtocol
```
ProtocolFilterWrapper和ProtocolListenerWrapper在發布registry協議時,不會添加監聽類和調用鏈,而是直接調用內部Protocol的export方法,最終會調用RegistryProtocol的export方法。
1. 根據配置的協議發布ref實現類的Invoker:在配置文件中,我們可能定義了多個類似`<dubbo:protocol name="dubbo" port="20880"/>`的協議,這里的目的就是將服務發布到各個協議中,與injvm協議類似,會根據url的協議使用不同的Protocol實現類,并被ProtocolFilterWrapper和ProtocolListenerWrapper包裝為協議鏈。后續對dubbo進行詳細說明。
2. 根據注冊中心的協議創建注冊中心
3. 將服務注冊到注冊中心
4. 向服務中心訂閱服務,訂閱時會添加監聽類
5. 返回一個Exporter,用于獲取服務信息,取消服務
由于每個注冊中心的實現不同,因此只做主要的流程說明,后續會對zookeeper注冊中心的實現進行說明,對injvm、dubbo協議進行說明。