ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 发布服务 在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&timestamp=1511357579872 ``` 最后,修改协议为registry,原先的协议放在参数registry的值中,最后我们获取了多个注册中心的url,如: ``` registry://224.5.6.7:1234/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&pid=753&registry=multicast&timestamp=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&timestamp=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协议进行说明。