为了账号安全,请及时绑定邮箱和手机立即绑定

Dubbo 源码分析-服务引用

标签:
Cocos2d-x

1. 简介

我们分析服务引用的原理。在 Dubbo 中,我们可以通过两种方式引用远程服务。第一种是使用服务直联的方式引用服务,第二种方式是基于注册中心进行引用。服务直联的方式仅适合在调试或测试服务的场景下使用,不适合在线上环境使用。因此,本文我将重点分析通过注册中心引用服务的过程。从注册中心中获取服务配置只是服务引用过程中的一环,除此之外,服务消费者还需要经历 Invoker 创建、代理类创建等步骤。这些步骤,我将在后续章节中一一进行分析。

2.服务引用原理

Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用。这两个引用服务的时机区别在于,第一个是饿汉式的,第二个是懒汉式的。默认情况下,Dubbo 使用懒汉式引用服务。如果需要使用饿汉式,可通过配置 <dubbo:reference> 的 init 属性开启。下面我们按照 Dubbo 默认配置进行分析,整个分析过程从 ReferenceBean 的 getObject 方法开始。当我们的服务被注入到其他类中时,Spring 会第一时间调用 getObject 方法,并由该方法执行服务引用逻辑。按照惯例,在进行具体工作之前,需先进行配置检查与收集工作。接着根据收集到的信息决定服务用的方式,有三种,第一种是引用本地 (JVM) 服务,第二是通过直联方式引用远程服务,第三是通过注册中心引用远程服务。不管是哪种引用方式,最后都会得到一个 Invoker 实例。如果有多个注册中心,多个服务提供者,这个时候会得到一组 Invoker 实例,此时需要通过集群管理类 Cluster 将多个 Invoker 合并成一个实例。合并后的 Invoker 实例已经具备调用本地或远程服务的能力了,但并不能将此实例暴露给用户使用,这会对用户业务代码造成侵入。此时框架还需要通过代理工厂类 (ProxyFactory) 为服务接口生成代理类,并让代理类去调用 Invoker 逻辑。避免了 Dubbo 框架代码对业务代码的侵入,同时也让框架更容易使用。

以上就是 Dubbo 引用服务的大致原理,下面我们深入到代码中,详细分析服务引用细节。

3.源码分析

服务引用的入口方法为 ReferenceBean 的 getObject 方法,该方法定义在 Spring 的 FactoryBean 接口中,ReferenceBean 实现了这个方法。实现代码如下:

public Object getObject() throws Exception {

return get();

}

public synchronized T get() {

if (destroyed) {

throw new IllegalStateException("Already destroyed!");

}

// 检测 ref 是否为空,为空则通过 init 方法创建

if (ref == null) {

// init 方法主要用于处理配置,以及调用 createProxy 生成代理类

init();

}

return ref;

}

这里两个方法代码都比较简短,并不难理解。不过这里需要特别说明一下,如果大家从 getObject 方法进行代码调试时,会碰到比较诧异的问题。这里假设你使用 IDEA,且保持了 IDEA 的默认配置。当你面调试到 get 方法的 if (ref == null) 时,你会惊奇的发现 ref 不为空,导致你无法进入到 init 方法中继续调试。导致这个现象的原因是 Dubbo 框架本身有点小问题,这个小问题会引发一些让人诧异的现象。关于这个问题,我进行了将近两个小时的排查。查明问题后,我给 Dubbo 提交了一个 pull request ( #2754 ) 修复了此问题。另外,beiwei30 前辈开了一个 issue ( #2757 ) 介绍这个问题,有兴趣的朋友可以去看看。大家如果想规避这个问题,可以修改一下 IDEA 的配置。在配置面板中搜索 toString,然后取消 Enable 'toString' object view 前的对号。具体如下:

webp

讲完需要注意的点,我们继续向下分析,接下来将分析配置的处理过程。

3.1 处理配置

Dubbo 提供了丰富的配置,用于调整和优化框架行为,性能等。Dubbo 在引用或导出服务时,首先会对这些配置进行检查和处理,以保证配置到正确性。如果大家不是很熟悉 Dubbo 配置,建议先阅读以下官方文档。配置解析的方法为 ReferenceConfig 的 init 方法,下面来看一下方法逻辑。

private void init() {

if (initialized) {

return;

}

initialized = true;

if (interfaceName == null || interfaceName.length() == 0) {

throw new IllegalStateException("interface not allow null!");

}

// 检测 consumer 变量是否为空,为空则创建

checkDefault();

appendProperties(this);

if (getGeneric() == null && getConsumer() != null) {

// 设置 generic

setGeneric(getConsumer().getGeneric());

}

// 检测是否为泛化接口

if (ProtocolUtils.isGeneric(getGeneric())) {

interfaceClass = GenericService.class;

} else {

try {

// 加载类

interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()

.getContextClassLoader());

} catch (ClassNotFoundException e) {

throw new IllegalStateException(e.getMessage(), e);

}

checkInterfaceAndMethods(interfaceClass, methods);

}

// -------------------------------:sparkles: 分割线1 :sparkles:------------------------------

// 从系统变量中获取与接口名对应的属性值

String resolve = System.getProperty(interfaceName);

String resolveFile = null;

if (resolve == null || resolve.length() == 0) {

// 从系统属性中获取解析文件路径

resolveFile = System.getProperty("dubbo.resolve.file");

if (resolveFile == null || resolveFile.length() == 0) {

// 从指定位置加载配置文件

File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");

if (userResolveFile.exists()) {

// 获取文件绝对路径

resolveFile = userResolveFile.getAbsolutePath();

}

}

if (resolveFile != null && resolveFile.length() > 0) {

Properties properties = new Properties();

FileInputStream fis = null;

try {

fis = new FileInputStream(new File(resolveFile));

// 从文件中加载配置

properties.load(fis);

} catch (IOException e) {

throw new IllegalStateException("Unload ..., cause:...");

} finally {

try {

if (null != fis) fis.close();

} catch (IOException e) {

logger.warn(e.getMessage(), e);

}

}

// 获取与接口名对应的配置

resolve = properties.getProperty(interfaceName);

}

}

if (resolve != null && resolve.length() > 0) {

// 将 resolve 赋值给 url

url = resolve;

}

// -------------------------------:sparkles: 分割线2 :sparkles:------------------------------

if (consumer != null) {

if (application == null) {

// 从 consumer 中获取 Application 实例,下同

application = consumer.getApplication();

}

if (module == null) {

module = consumer.getModule();

}



作者:Java邵先生
链接:https://www.jianshu.com/p/655a15e1c597


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消