服务引用发生在spring容器启动的时候,在容器启动时,spring会扫描dubbo自定义的相关xml schema,并通过自定义的DubboNamespaceHandler去处理,在DubboNamespaceHandler中注册了dubbo相关的所有标签,统一由DubboBeanDefinitionParser去处理解析。
服务引用标签对应的解析目标类是ReferenceBean,ReferenceBean维护了整个服务的生命周期,是服务引用的关键。
在spring容器进行初始化时,会将单例的bean放入容器中维护起来,也就是说ReferenceBean会在spring容器启动过程中初始化。 可以理解为饿汉式单例模式,但它严格意义上来讲,并不是单例模式。
并且ReferenceBean还实现了一系列spring相关的接口,参与到spring容器启动的生命周期,比如FactoryBean、ApplicationContextAware、InitializingBean以及DisposableBean;FactoryBean会告诉容器具体注入的bean的类型,ApplicationContextAware的作用是将spring的应用上下文注入到SpringExtensionFactory,InitializingBean主要做的是在属性赋值之后,初始化之前,对属性做一些设置和校验,并且最终调用getObject(),将服务代理类注入容器。
引用目的
方便客户端透明的调用远程服务。
具体流程分析
- 官方消费服务详细过程图:
从图上可知,服务引用大概就是将远端服务转化为Invoker,然后根据Invoker生成代理的过程。 - ReferenceBean#getObject()–>ReferenceConfig#get()
*ReferenceConfig#get()–>ReferenceConfig#init()
防止重复引用,具体的引用逻辑下图中的代码完成 - ReferenceConfig#init()–》ReferenceConfig#createProxy()
- 首先判断是否同一jvm引用服务
- 如果不是同一jvm引用,则判断是否是对点对直连地址
- 如果不是点对点直连,则加载注册中心连接,通过注册中心配置拼装服务URL
- 如果获取的远程服务url地址有多个(服务提供者集群),那么将会通过invoker集合和URL构造一个Directory,然后加入集群,返回一个AbstractClusterInvoker的实例。
集群相关需要关注的几个spi如下图:
Cluster和Invoker的链接处如下图:
也就是说集群情况下,返回的invoker是一个AbstractClusterInvoker的实例。 - 如果远程服务提供者只有一个,那么直接引用,refprotocol.refer()
综上所述,我们现阶段只需要关注refprotocol.refer()的逻辑就可。
- ReferenceConfig#createProxy()–》Protocol$Adpative.refer()
据上可知,最终调用的Protocol是RegistryProcotol - Protocol$Adpative.refer()–》ProtocolFilterWrapper.refer()
因为协议是registry,所以不做处理 - ProtocolFilterWrapper.refer()–》ProtocolListenerWrapper.refer()
因为协议是registry,所以不做处理 - ProtocolListenerWrapper.refer()–》RegistryProtocol.refer()
- RegistryProtocol.refer()–》registryFactory.getRegistry(url)
- registryFactory.getRegistry(url)–》RegistryFactory$Adpative.getRegistry()
注册中心为zookeeper - RegistryFactory$Adpative.getRegistry()–》AbstractRegistryFactory.getRegistry()
获取注册中心AbstractRegistryFactory提供了统一处理的方案
(1) 从REGISTRIES缓存(Map<String, Registry>)中获取
(2)如果缓存中不存在,则调用createRegistry(url)创建
调用createRegistry(url)创建过程上篇已经分析过,这里不在赘述。
(3)将注册中心实例放入缓存,并返回 - AbstractRegistryFactory.getRegistry()–》StringUtils.parseQueryString()
获取请求信息 - StringUtils.parseQueryString()–》RegistryProtocol.doRefer()
(1)构造一个RegistryDirectory
(2)构造订阅服务链接
(3)向zookeeper注册消费端地址consumer://30.26.211.140/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=174404&side=consumer×tamp=1542190681032
(4)向zookeeper订阅服务在zookeeper上的节点地址,在订阅节点发生变化时触发FailbackRegistry#notify,从而实现本地缓存文件的更新以及通知到RegistryProtocol.OverrideListener#notify(),重新生成invoker可执行对象consumer://30.26.211.140/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&category=consumers&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=174404&side=consumer×tamp=1542190681032
## RegistryDirectory 增加router的Directory public void subscribe(URL url) { setConsumerUrl(url); registry.subscribe(url, this); }
(5)生成Invoker
我们可以看到dubbo默认的容错策略是失败转移(Failover),并且外层会有一个MockClusterWrapper的包装类,用于本地伪装,做服务降级。
到此为止远端服务装化为Invoker的过程就结束了。
- 创建代理的流程
- ReferenceConfig.proxyFactory.getProxy()–>ProxyFactory$Adpative.getProxy()
由上可知,默认使用javassist来实现代理,JavassistProxyFactory类图如下:
- ProxyFactory$Adpative.getProxy()–>StubProxyFactoryWrapper.getProxy()
- StubProxyFactoryWrapper.getProxy()–》AbstractProxyFactory.getProxy()
- AbstractProxyFactory.getProxy()–》JavassistProxyFactory.getProxy()
- JavassistProxyFactory.getProxy()–》Proxy.getProxy()
(1)PendingGenerationMarker:保证每个代理类当前只允许有一个线程生成。
(2)代理类的存放采用new WeakReference(proxy)的方式,方便和加快垃圾回收,避免长时间不用的代理对象长驻内存 Proxy.getProxy()–》Proxy.newInstance()
(3)ClassGenerator:dubbo自已对javassist的封装
到此创建服务代理就完结了,完成了从Invoker到ref的转换。 - 然后将创建好的代理类维护到spring容器中,让spring管理起来。
具体的位置在
也就是说FactoryBean创建的对象都在factoryBeanObjectCache中保存。这样就可以方便的从容器中获取代理服务,执行相应的操作。
至此,整个引用流程就结束了。
引用总结
服务引用简单来说,就是文章开篇官方图描绘的那样,需要注意的几点是:引用中获取的Invoker是经过包装的(集群容错等伪装成一个Invoker);引用过程会处理是否本地暴露以及点对点直连;注册的zookeeper节点发生变化时,会刷新本地缓存;默认是由javassist创建代理服务,同时也提供jdk创建代理服务的方式。
简单时序图
官方时序图
下集预告:服务消费