<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文件如下:
文件内容如下:
相应的命名空间使用相应的namespacehandler
dubbo就是自定义类型的,所以也要给出namespacehandler、beandefinitionparser。namespacehandler是dubbonamespacehandler:
给出的beandefinitionparser全部是dubbobeandefinitionparser,如果我们想看看<dubbo:registry>是怎么解析的,就可以去看看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的实现情况:
对于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的实现情况:
可以看到在返回dubboprotocol之前,经过了protocolfilterwrapper、protocollistenerwrapper、registryprotocol的包装。
所谓的包装就是如下类似的内容:
使用装饰器模式,类似aop的功能。
下面主要讲解registryprotocol和dubboprotocol,先暂时忽略protocolfilterwrapper、protocollistenerwrapper
所以上述服务发布的过程
会先经过registryprotocol,它干了哪些事呢?
利用内部的protocol即dubboprotocol,将服务进行导出,如下
exporter = protocol.export(new invokerwrapper<t>(invoker, url));
根据注册中心的registryurl获取注册服务registry,然后将serviceurl注册到注册中心上,供客户端订阅
registry registry = registryfactory.getregistry(registryurl); registry.register(serviceurl)
来详细看看上述dubboprotocol的服务导出功能:
首先根据invoker的url获取exchangeserver通信对象(负责与客户端的通信模块),以url中的host和port作为key存至map<string, exchangeserver> servermap中。即可以采用全部服务的通信交给这一个exchangeserver通信对象,也可以某些服务单独使用新的exchangeserver通信对象。
string key = url.getaddress(); //client 也可以暴露一个只有server可以调用的服务。 boolean isserver = url.getparameter(rpcconstants.is_server_key,true); if (isserver && ! servermap.containskey(key)) {
}
创建一个dubboexporter,封装invoker。然后根据url的port、path(接口的名称)、版本号、分组号作为key,将dubboexporter存至map<string, exporter<?>> exportermap中
key = servicekey(url); dubboexporter<t> exporter = new dubboexporter<t>(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<string, exporter<?>> exportermap中获取exporter,然后就可以找到对应的invoker了,就可以顺利的调用服务了。
而对于通信这一块,接下来会专门来详细的说明。
负责维护invoker的生命周期。接口定义如下:
包含了一个invoker对象。一旦想撤销该服务,就会调用invoker的destroy()方法,同时清理上述exportermap中的数据。对于registryprotocol来说就需要向注册中心撤销该服务。
本文简略地介绍了接入spring过程的原理,以及服务发布过程中的几个概念。接下来的打算是:
客户端订阅服务与使用服务涉及的概念
注册中心模块
客户端与服务器端网络通信模块