服务暴露了,也注册了,接下来就需要消费者来引用了。
关于本文
引用分为两种:服务直连和基于注册中心。直连是用于调试和测试服务的,不适合用于线上环境。以下将结合dubbo官网源码解读-服务引入和dubbo源码(与文档平配套的2.6.4和笔者此时的最新版)来谈谈笔者的一点学习内容。
一、简要介绍
此为服务引用时序图
引用服务
1. 直连引用服务:
在没有注册中心,直连提供者的情况下,
ReferenceConfig
解析出的 URL 的格式为:dubbo://service-host/com.foo.FooService?version=1.0.0
。基于扩展点自适应机制,通过 URL 的
dubbo://
协议头识别,直接调用DubboProtocol
的refer()
方法,返回提供者引用。2. 从注册中心发现引用服务:
在有注册中心,通过注册中心发现提供者地址的情况下,
ReferenceConfig
解析出的 URL 的格式为:registry://registry-host/org.apache.dubbo.registry.RegistryService?refer=URL.encode("consumer://consumer-host/com.foo.FooService?version=1.0.0")
。基于扩展点自适应机制,通过 URL 的
registry://
协议头识别,就会调用RegistryProtocol
的refer()
方法,基于refer
参数中的条件,查询提供者 URL,如:dubbo://service-host/com.foo.FooService?version=1.0.0
。基于扩展点自适应机制,通过提供者 URL 的
dubbo://
协议头识别,就会调用DubboProtocol
的refer()
方法,得到提供者引用。然后
RegistryProtocol
将多个提供者引用,通过Cluster
扩展点,伪装成单个提供者引用返回。
dubbo服务引用有两类方式:饿汉式和懒汉式。dubbo默认为懒汉式,笔者这里也会着重根据文档分析懒汉式(是真的懒),之后也许会谈到一些饿汉式的内容。
懒汉式即在ReferenceBean
对应的服务被注入到其他类中时引用,于是我们会从ReferenceBean.getObject
方法开始我们的讨论。引用服务也分为三种,分别为引用本地服务(JVM),通过直连引用远程服务(调试测试)以及利用注册中心进行引用。这三种都会产生Invoker实例,该实例具备调用服务的能力,但不会直接暴露给用户,会像服务暴露一样,通过代理工厂类(ProxyFactory
)来生成代理类调用Invoker逻辑。避免了Dubbo框架代码对业务代码的侵入,同时使得框架更易于使用。
接下来我会像前一篇一样搞个有序列表出来,然后再分析每一步做了哪些东西以及两个版本源码的异同。
二、过程解析
引用服务的过程:
从ReferenceBean.getObject
开始
-
调用
ReferenceConfig.get
方法,以获得ref-
ref为空,则调用
init
方法创建-
2.6.4源码中,
init
方法有点长,官网文档将其分为六部分,此处我先给出文档里的一点注解。首先是方法开始到分割线1之间的代码。这段代码主要用于检测
ConsumerConfig
实例是否存在,如不存在则创建一个新的实例,然后通过系统变量或dubbo.properties
配置文件填充ConsumerConfig
的字段。接着是检测泛化配置,并根据配置设置interfaceClass
的值。接着来看分割线1到分割线2之间的逻辑。这段逻辑用于从系统属性或配置文件中加载与接口名相对应的配置,并将解析结果赋值给 url 字段。url 字段的作用一般是用于点对点调用。继续向下看,分割线2和分割线3之间的代码用于检测几个核心配置类是否为空,为空则尝试从其他配置类中获取。分割线3与分割线4之间的代码主要用于收集各种配置,并将配置存储到 map 中。分割线4和分割线5之间的代码用于处理MethodConfig
实例。该实例包含了事件通知配置,比如onreturn
、onthrow
、oninvoke
等。分割线5到方法结尾的代码主要用于解析服务消费者ip
,以及调用createProxy
创建代理对象。 -
最新的源码中将各部分拆开,分成多个方法,以下以Ⅰ、Ⅱ、Ⅲ、Ⅳ、Ⅴ、Ⅵ来表示2.6.4源码中的各部分,笔者将会找出新版源码中各部分的实现的位置。
- Ⅰ:位于
checkAndUpdateSubConfigs
中,该方法在get
中被调用 - Ⅱ:位于
resolveFile
中,该方法在checkAndUpdateSubConfigs
中被调用 - Ⅲ:位于
completeCompoundConfigs
中,该方法在checkAndUpdateSubConfigs
中被调用 - Ⅳ、Ⅴ、Ⅵ:位于
init
中
- Ⅰ:位于
-
-
调用
createProxy
,该方法不仅用来创建对象,还会调用其他方法构建及合并Invoker实例- 判断是否做本地调用
- 做则处理本地调用,生成本地引用URL,协议为injvm,调用
refer
方法构建InjvmInvoker
实例 - 不做则处理远程调用
- url不为空,则可能进行点对点调用,将url一条条处理,判断是否为registry,若是,则有指定的注册中心;否则合并url,最后将合并后的配置设置为url查询字符串中
- url为空,加载注册中心url,未配置则抛出异常
- 若只有单个注册中心或提供者,调用
RegistryProtocol.refer
构建Invoker实例;否则根据url协议头分别调用指定的protocol.refer
,创建StaticDirectory
实例,并由Cluster对多个Invoker进行合并
- 检查Invoker可用性
- 生成代理类
-
-
定位:3.1 处理配置
如上,
Reference.init
方法被分割成多个方法然后相互调用,具体结合上面的分析和源码。对于
attributes
,文档里使用StaticContext.getSystemContext().putAll(attributes)
一句将其存储到系统上下文中,而在新版源码里,则是使用ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes))
将其加入了ConsumerModel
的构造里。 -
定位: 3.2 引用服务
和前篇里很多优化一样,新版源码用
shouldJvmRefer
方法取代了原本的isInjvm
方法,使得判断过程从主方法里完全移除。dubbo自2.7.0开始增加了元数据相关内容,多出如下代码:
/** * @since 2.7.0 * ServiceData Store */ MetadataReportService metadataReportService = null; if ((metadataReportService = getMetadataReportService()) != null) { URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map); metadataReportService.publishConsumer(consumerURL); }
创建Invoker 的过程(各Protocol的refer
方法)
-
DubboProtocol.refer
(新版源码里为protocolBindingRefer
),调用getClients
方法来获取客户端实例- 根据connections数量决定是获取共享客户端还是创建新的客户端实例(默认为使用共享客户端),此处还会涉及
getSharedClient
方法-
getSharedClient
方法中,会获取带有“引用计数”功能的ExchangeClient
并会按操作增加其引用计数。 - 未命中缓存则通过
initClient
创建ExchangeClient
客户端,将其实例传给ReferenceCountExchangeClient
(使用装饰模式)(此处有些许出入,新版源码里的getSharedClient
会调用buildReferenceCountExchangeClient
再由其调用initClient
) -
initClient
获取用户配置的客户端类型(默认为netty),若不存在抛出异常 - 根据lazy配置决定创建客户端(懒加载或普通),其中懒加载的相关代码不复杂且也会调用
Exchangers.connect
,故直接分析该方法
-
-
Exchangers.connect
需要通过getExchanger
获取Exchanger
实例,默认为HeaderExchangeClient
-
getExchanger
通过SPI加载HeaderExchangeClient
实例 - 关于
HeaderExchangeClient
的实现(HeaderExchanger.connect
,和上次差不多,套了四层,也只要看一下Transporters.connect
)new HeaderExchangeHandler(handler)
new DecodeHandler(new HeaderExchangeHandler(handler))
-
Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
- 其中的
getTransporter
方法和服务暴露里的几乎一致,默认为nettyTranspoter
并调用其connect
方法
- 其中的
new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true)
-
- 根据connections数量决定是获取共享客户端还是创建新的客户端实例(默认为使用共享客户端),此处还会涉及
-
RegistryProtocol.refer
- 为url设置协议头,并根据url参数加载注册中心实例
- 获取group配置,来获得
doRefer
第一个参数的类型- 创建
RegistryDirectory
实例 - 生成服务者消费者链接并向注册中心注册
- 订阅数据,使
RegistryDirectory
收到(providers里的多节点将使用Cluster进行合并)
- 创建
-
定位:3.2.1 创建Invoker
DubboProtocol.getSharedClient
在新版源码里会间接调用DubboProtocol.initClient
创建代理(AbstractProxyFactory.getProxy
)
- 获取interfaces数组
- 调用
getProxy
抽象方法(JavassistProxyFactory.getProxy
)- 生成Proxy子类
- 调用Proxy子类的
newInstance
创建Proxy实例(Proxy.getProxy
)- 检测接口列表,将接口整合后作为key使用
- 用key在缓存里获取
Reference<Proxy>
实例 - 并发控制,使得有且仅有一个线程进行后续操作
- 创建
ClassGenerator
对象,遍历接口以加入ccp(用于服务接口生成代理类)中。此时对于非public的接口(即protected或private的接口)需要检测包名,不在同一个包下回抛出异常 - 对于每个接口,遍历方法,将描述(
ReflectUtils.getDesc
)整合,若已在worked中则会忽略。然后向ccp中添加方法名,访问控制符,参数列表,方法代码等信息 - 构建接口代理类名称,生成handler,添加带有
InvocationHandler
参数的构造方法ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], "handler=$1;")
和默认的构造方法 - 生成接口代理类
- 构建Proxy子类,包括名称(Proxy + id),默认构造方法,父类(Proxy),
newInstance
方法。生成实现类并通过反射创建Proxy实例