# 发布服务
在Spring的ClassPathXmlApplicationContext初始化结束后,会触发ApplicationEvent事件;dubbo使用`<dubbo:service>`发布服务,该标签对应的是ServiceBean,这个类实现了ApplicationListener接口,并在ContextRefreshedEvent事件时调用export()发布服务。
![服务发布流程](http://www.uxiaowo.com/dubbo/export.png)
## 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协议进行说明。