天天看点

dubbo源码分析系列(2)服务的发布1 系列目录2 dubbo与spring接入3 服务的发布过程4 结束语

<a href="http://my.oschina.net/pingpangkuangmo/blog/508963">dubbo源码分析系列(1)扩展机制的实现</a>

<a href="http://my.oschina.net/pingpangkuangmo/blog/511766">dubbo源码分析系列(2)服务的发布</a>

<a href="http://my.oschina.net/pingpangkuangmo/blog/515673">dubbo源码分析系列(3)服务的引用</a>

<a href="http://my.oschina.net/pingpangkuangmo/blog/521945">dubbo源码分析系列(4)dubbo通信设计</a>

dubbo的官方文档也说明了,dubbo可以不依赖任何spring。这一块日后再详细说明,目前先介绍dubbo与spring的集成。与spring的集成是基于spring的schema扩展进行加载

用过spring就知道可以在xml文件中进行如下配置:

spring是如何来解析这些配置呢?如果我们想自己定义配置该如何做呢?

对于上述的xml配置,分成三个部分

命名空间namespace,如tx、context

元素element,如component-scan、property-placeholder、annotation-driven

属性attribute,如base-package、location、transaction-manager

spring定义了两个接口,来分别解析上述内容:

namespacehandler:注册了一堆beandefinitionparser,利用他们来进行解析

beandefinitionparser: 用于解析每个element的内容

来看下具体的一个案例,就以spring的context命名空间为例,对应的namespacehandler实现是contextnamespacehandler:

注册了一堆beandefinitionparser,如果我们想看"component-scan"是如何实现的,就可以去看对应的componentscanbeandefinitionparser的源码了

如果自定义了namespacehandler,如何加入到spring中呢?

spring默认会在加载jar包下的 meta-inf/spring.handlers文件下寻找namespacehandler,默认的spring文件如下:

dubbo源码分析系列(2)服务的发布1 系列目录2 dubbo与spring接入3 服务的发布过程4 结束语

文件内容如下:

相应的命名空间使用相应的namespacehandler

dubbo就是自定义类型的,所以也要给出namespacehandler、beandefinitionparser。namespacehandler是dubbonamespacehandler:

给出的beandefinitionparser全部是dubbobeandefinitionparser,如果我们想看看&lt;dubbo:registry&gt;是怎么解析的,就可以去看看dubbobeandefinitionparser的源代码。

而dubbo的jar包下,存在着meta-inf/spring.handlers文件,内容如下:

具体解析过程就不再说明了。结果就是不同的配置分别转换成spring容器中的一个bean对象。

application对应applicationconfig

registry对应registryconfig

monitor对应monitorconfig

provider对应providerconfig

consumer对应consumerconfig

protocol对应protocolconfig

service对应serviceconfig

reference对应referenceconfig

上面的对象不依赖spring,也就是说你可以手动去创建上述对象。

为了在spring启动的时候,也相应的启动provider发布服务注册服务的过程:又加入了一个和spring相关联的servicebean,继承了serviceconfig

为了在spring启动的时候,也相应的启动consumer发现服务的过程:又加入了一个和spring相关联的referencebean,继承了referenceconfig

利用spring就做了上述过程,得到相应的配置数据,然后启动相应的服务。如果想剥离spring,我们就可以手动来创建上述配置对象,通过serviceconfig和referenceconfig的api来启动相应的服务

从上面知道,利用spring的解析收集到很多一些配置,然后将这些配置都存至serviceconfig中,然后调用serviceconfig的export()方法来进行服务的发布与注册

先看一个简单的服务端例子,dubbo配置如下:

有一个服务接口,helloservice,以及它对应的实现类helloserviceimpl

将helloservice标记为dubbo服务,使用helloserviceimpl对象来提供具体的服务

使用zookeeper作为注册中心

一个服务可以有多个注册中心、多个服务协议

多注册中心信息:

首选根据注册中心配置,即上述的zookeeper配置信息,将注册信息聚合在一个url对象中,registryurls内容如下:

多协议信息:

由于上述我们没有配置任何协议信息,就会使用默认的dubbo协议,开放在20880端口,也就是在该端口,对外提供上述的helloservice服务,注册的协议信息也转化成一个url对象,如下:

依据注册中心信息和协议信息的组合起来,依次来进行服务的发布。整个过程伪代码如下:

所以服务发布过程大致分成两步:

第一步:通过proxyfactory将helloserviceimpl封装成一个invoker

第二步:使用protocol将invoker导出成一个exporter

这里面就涉及到几个大的概念。proxyfactory、invoker、protocol、exporter。下面来一一介绍

分别介绍下invoker、proxyfactory、protocol、exporter的概念

invoker: 一个可执行的对象,能够根据方法名称、参数得到相应的执行结果。接口内容简略如下:

而invocation则包含了需要执行的方法、参数等信息,接口定义简略如下:

目前其实现类只有一个rpcinvocation。内容大致如下:

仅仅提供了invocation所需要的参数而已,继续回到invoker

这个可执行对象的执行过程分成三种类型:

类型1:本地执行类的invoker

类型2:远程通信执行类的invoker

类型3:多个类型2的invoker聚合成的集群版的invoker

以helloservice接口方法为例:

本地执行类的invoker: server端,含有对应的helloserviceimpl实现,要执行该接口方法,仅仅只需要通过反射执行helloserviceimpl对应的方法即可

远程通信执行类的invoker: client端,要想执行该接口方法,需要需要进行远程通信,发送要执行的参数信息给server端,server端利用上述本地执行的invoker执行相应的方法,然后将返回的结果发送给client端。这整个过程算是该类invoker的典型的执行过程

集群版的invoker:client端,拥有某个服务的多个invoker,此时client端需要做的就是将这个多个invoker聚合成一个集群版的invoker,client端使用的时候,仅仅通过集群版的invoker来进行操作。集群版的invoker会从众多的远程通信类型的invoker中选择一个来执行(从中加入负载均衡策略),还可以采用一些失败转移策略等

所以来看下invoker的实现情况:

dubbo源码分析系列(2)服务的发布1 系列目录2 dubbo与spring接入3 服务的发布过程4 结束语

对于server端,主要负责将服务如helloserviceimpl统一进行包装成一个invoker,这些invoker通过反射来执行具体的helloserviceimpl对象的方法。

接口定义如下:

proxyfactory的接口实现有jdkproxyfactory、javassistproxyfactory,默认是javassistproxyfactory, jdkproxyfactory内容如下:

可以看到是创建了一个abstractproxyinvoker(这类就是本地执行的invoker),它对invoker的result invoke(invocation invocation)实现如下:

综上所述,服务发布的第一个过程就是:

使用proxyfactory将helloserviceimpl封装成一个本地执行的invoker。

从上面得知服务发布的第一个过程就是:

执行这个服务,即执行这个本地invoker,即调用这个本地invoker的invoke(invocation invocation)方法,方法的执行过程就是通过反射执行了helloserviceimpl的内容。现在的问题是:这个方法的参数invocation invocation的来源问题。

针对server端来说,protocol要解决的问题就是:根据指定协议对外公布这个helloservice服务,当客户端根据协议调用这个服务时,将客户端传递过来的invocation参数交给上述的invoker来执行。所以protocol加入了远程通信协议的这一块,根据客户端的请求来获取参数invocation invocation。

先来看下protocol的接口定义:

我们再来详细看看服务发布的第二步:

protocol的来历是:

export(invoker invoker)的过程即根据invoker中url的配置信息来最终选择的protocol实现,默认实现是"dubbo"的扩展实现即dubboprotocol,然后再对dubboprotocol进行依赖注入,进行wrap包装。先来看看protocol的实现情况:

dubbo源码分析系列(2)服务的发布1 系列目录2 dubbo与spring接入3 服务的发布过程4 结束语

可以看到在返回dubboprotocol之前,经过了protocolfilterwrapper、protocollistenerwrapper、registryprotocol的包装。

所谓的包装就是如下类似的内容:

使用装饰器模式,类似aop的功能。

下面主要讲解registryprotocol和dubboprotocol,先暂时忽略protocolfilterwrapper、protocollistenerwrapper

所以上述服务发布的过程

会先经过registryprotocol,它干了哪些事呢?

利用内部的protocol即dubboprotocol,将服务进行导出,如下

exporter = protocol.export(new invokerwrapper&lt;t&gt;(invoker, url));

根据注册中心的registryurl获取注册服务registry,然后将serviceurl注册到注册中心上,供客户端订阅

registry registry = registryfactory.getregistry(registryurl); registry.register(serviceurl)

来详细看看上述dubboprotocol的服务导出功能:

首先根据invoker的url获取exchangeserver通信对象(负责与客户端的通信模块),以url中的host和port作为key存至map&lt;string, exchangeserver&gt; servermap中。即可以采用全部服务的通信交给这一个exchangeserver通信对象,也可以某些服务单独使用新的exchangeserver通信对象。

string key = url.getaddress(); //client 也可以暴露一个只有server可以调用的服务。 boolean isserver = url.getparameter(rpcconstants.is_server_key,true); if (isserver &amp;&amp; ! servermap.containskey(key)) {

}

创建一个dubboexporter,封装invoker。然后根据url的port、path(接口的名称)、版本号、分组号作为key,将dubboexporter存至map&lt;string, exporter&lt;?&gt;&gt; exportermap中

key = servicekey(url); dubboexporter&lt;t&gt; exporter = new dubboexporter&lt;t&gt;(invoker, key, exportermap); exportermap.put(key, exporter);

现在我们要搞清楚我们的目的:通过通信对象获取客户端传来的invocation invocation参数,然后找到对应的dubboexporter(即能够获取到本地invoker)就可以执行服务了。

上述每一个exchangeserver通信对象都绑定了一个exchangehandler requesthandler对象,内容简略如下:

可以看到在获取到invocation参数后,调用getinvoker(channel, inv)来获取本地invoker。获取过程就是根据channel获取port,根据invocation inv信息获取要调用的服务接口、版本号、分组号等,以此组装成key,从上述map&lt;string, exporter&lt;?&gt;&gt; exportermap中获取exporter,然后就可以找到对应的invoker了,就可以顺利的调用服务了。

而对于通信这一块,接下来会专门来详细的说明。

负责维护invoker的生命周期。接口定义如下:

包含了一个invoker对象。一旦想撤销该服务,就会调用invoker的destroy()方法,同时清理上述exportermap中的数据。对于registryprotocol来说就需要向注册中心撤销该服务。

本文简略地介绍了接入spring过程的原理,以及服务发布过程中的几个概念。接下来的打算是:

客户端订阅服务与使用服务涉及的概念

注册中心模块

客户端与服务器端网络通信模块