Java基础教程:dubbo源码解析-服务暴露与发现

本文深入探讨了Dubbo服务暴露和发现的原理,包括Spring自定义Schema、服务暴露的整体流程、服务发现的详细步骤。通过源码分析,解释了ServiceBean、ReferenceBean、RegistryConfig等关键对象的作用,以及服务如何通过Invoker和Protocol进行暴露和引用。文章还详细介绍了服务暴露的本地与远程导出,以及服务注册到Zookeeper的过程。
摘要由CSDN通过智能技术生成

概述

dubbo是一个简单易用的RPC框架,通过简单的提供者,消费者配置就能完成无感的网络调用。那么在dubbo中是如何将提供者的服务暴露出去,消费者又是如何获取到提供者相关信息的呢?这就是本章我们要讨论的内容。

dubbo与spring的整合

在了解dubbo的服务注册和服务发现之前,我们首先需要掌握一个知识点:Spring中自定义Schema。

Spring自定义Schema

Dubbo 现在的设计是完全无侵入,也就是使用者只依赖于配置契约。在 Dubbo 中,可以使用 XML 配置相关信息,也可以用来引入服务或者导出服务。配置完成,启动工程,Spring 会读取配置文件,生成注入相关Bean。那 Dubbo 如何实现自定义 XML 被 Spring 加载读取呢?

从 Spring 2.0 开始,Spring 开始提供了一种基于 XML Schema 格式扩展机制,用于定义和配置 bean。

入门案例

学习和使用Spring XML Schema 扩展机制并不难,需要下面几个步骤:

  1. 创建配置属性的JavaBean对象
  2. 创建一个 XML Schema 文件,描述自定义的合法构建模块,也就是xsd文件。
  3. 自定义处理器类,并实现NamespaceHandler接口。
  4. 自定义解析器,实现BeanDefinitionParser接口(最关键的部分)。
  5. 编写Spring.handlers和Spring.schemas文件配置所有部件

定义JavaBean对象,在spring中此对象会根据配置自动创建

public class User {
   
	private String id;  
    private String name;  
    private Integer age;
    //省略getter setter方法
}

在META-INF下定义user.xsd文件,使用xsd用于描述标签的规则

<?xml version="1.0" encoding="UTF-8"?>  
<xsd:schema   
    xmlns="http://www.itheima.com/schema/user"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"   
    xmlns:beans="http://www.springframework.org/schema/beans"  
    targetNamespace="http://www.itheima.com/schema/user"
    elementFormDefault="qualified"   
    attributeFormDefault="unqualified">  
    <xsd:import namespace="http://www.springframework.org/schema/beans" />  
    <xsd:element name="user">
        <xsd:complexType>  
            <xsd:complexContent>  
                <xsd:extension base="beans:identifiedType">  
                    <xsd:attribute name="name" type="xsd:string" />  
                    <xsd:attribute name="age" type="xsd:int" />  
                </xsd:extension>  
            </xsd:complexContent>  
        </xsd:complexType>  
    </xsd:element>  
</xsd:schema> 

Spring读取xml文件时,会根据标签的命名空间找到其对应的NamespaceHandler,我们在NamespaceHandler内会注册标签对应的解析器BeanDefinitionParser。

package com.itheima.schema;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class UserNamespaceHandler extends NamespaceHandlerSupport {
   
	public void init() {
   
		registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
	}
}

BeanDefinitionParser是标签对应的解析器,Spring读取到对应标签时会使用该类进行解析;

public class UserBeanDefinitionParser extends
		AbstractSingleBeanDefinitionParser {
   
	
    protected Class getBeanClass(Element element) {
   
		return User.class;
	}

	protected void doParse(Element element, BeanDefinitionBuilder bean) {
   
		String name = element.getAttribute("name");
		String age = element.getAttribute("age");
		String id = element.getAttribute("id");
		if (StringUtils.hasText(id)) {
   
			bean.addPropertyValue("id", id);
		}
		if (StringUtils.hasText(name)) {
   
			bean.addPropertyValue("name", name);
		}
		if (StringUtils.hasText(age)) {
   
			bean.addPropertyValue("age", Integer.valueOf(age));
		}
	}
}

定义spring.handlers文件,内部保存命名空间与NamespaceHandler类的对应关系;必须放在classpath下的META-INF文件夹中。

http\://www.itheima.com/schema/user=com.itheima.schema.UserNamespaceHandler

定义spring.schemas文件,内部保存命名空间对应的xsd文件位置;必须放在classpath下的META-INF文件夹中。

http\://www.itheima.com/schema/user.xsd=META-INF/user.xsd

代码准备好了之后,就可以在spring工程中进行使用和测试,定义spring配置文件,导入对应约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:context="http://www.springframework.org/schema/context" 
xmlns:util="http://www.springframework.org/schema/util" 
xmlns:task="http://www.springframework.org/schema/task" 
xmlns:aop="http://www.springframework.org/schema/aop" 
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:itheima="http://www.itheima.com/schema/user"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
		http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.itheima.com/schema/user http://www.itheima.com/schema/user.xsd">

    <itheima:user id="user" name="zhangsan" age="12"></itheima:user>

</beans>

编写测试类,通过spring容器获取对象user

public class SchemaDemo {
   
	public static void main(String[] args) {
   
		ApplicationContext ctx = new ClassPathXmlApplicationContext("/spring/applicationContext.xml");
		User user = (User)ctx.getBean("user");
		System.out.println(user);
	}
}

dubbo中的相关对象

Dubbo是运行在spring容器中,dubbo的配置文件也是通过spring的配置文件applicationContext.xml来加载,所以dubbo的自定义配置标签实现,其实同样依赖spring的xml schema机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nOowZByb-1663573584460)(assets/1591927147303.png)]

可以看出Dubbo所有的组件都是由DubboBeanDefinitionParser解析,并通过registerBeanDefinitionParser方法来注册到spring中最后解析对应的对象。这些对象中我们重点关注的有以下两个:

  • ServiceBean:服务提供者暴露服务的核心对象
  • ReferenceBean:服务消费者发现服务的核心对象
  • RegistryConfig:定义注册中心的核心配置对象

服务暴露

前面主要探讨了 Dubbo 中 schema 、 XML 的相关原理 , 这些内容对理解框架整体至关重要 , 在此基础上我们继续探讨服务是如何依靠前面的配置进行服务暴露

名词解释

在 Dubbo 的核心领域模型中:

  • Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。在服务提供方,Invoker用于调用服务提供类。在服务消费方,Invoker用于执行远程调用。
  • Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
    • export:暴露远程服务
    • refer:引用远程服务
  • proxyFactory:获取一个接口的代理类
    • getInvoker:针对server端,将服务对象,如DemoServiceImpl包装成一个Invoker对象
    • getProxy:针对client端,创建接口的代理对象,例如DemoService的接口。
  • Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等

整体流程

在详细探讨服务暴露细节之前 , 我们先看一下整体duubo的服务暴露原理

在这里插入图片描述

在整体上看,Dubbo 框架做服务暴露分为两大部分 , 第一步将持有的服务实例通过代理转换成 Invoker, 第二步会把 Invoker 通过具体的协议 ( 比如 Dubbo ) 转换成 Exporter, 框架做了这层抽象也大大方便了功能扩展 。

服务提供方暴露服务的蓝色初始化链,时序图如下:

在这里插入图片描述

源码分析

(1) 导出入口

服务导出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务导出操作。方法代码如下:

public void onApplicationEvent(ContextRefreshedEvent event) {
   
    // 是否有延迟导出 && 是否已导出 && 是不是已被取消导出
    if (isDelay() && !isExported() && !isUnexported()) {
   
        // 导出服务
        export();
    }
}

onApplicationEvent 方法在经过一些判断后,会决定是否调用 export 方法导出服务。在export 根据配置执行相应的动作。最终进入到doExportUrls导出服务方法

private void doExportUrls() {
   
    // 加载注册中心链接
    List<URL> registryURLs = loadRegistries(true);
    // 遍历 protocols,并在每个协议下导出服务
    for (ProtocolConfig protocolConfig : protocols) {
   
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

关于多协议多注册中心导出服务首先是根据配置,以及其他一些信息组装 URL。前面说过,URL 是 Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
   
    String name = protocolConfig.getName();
    // 如果协议名为空,或空串,则将协议名变量设置为 dubbo
    if (name == null || name.length() == 0) {
   
        name = "dubbo";
    }

    Map<String, String> map = new HashMap<String, String>();
	
    //略

    // 获取上下文路径
    String contextPath = protocolConfig.getContextpath();
    if ((contextPath == null || contextPath.length() == 0) && provider != null) {
   
        contextPath = provider.getContextpath();
    }

    // 获取 host 和 port
    String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
    Integer port = this.findConfigedPorts(protocolConfig, name, map);
    // 组装 URL
    URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
    
    // 省略无关代码
}

上面的代码首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。前置工作做完,接下来就可以进行服务导出了。服务导出分为导出到本地 (JVM),和导出到远程。在深入分析服务导出的源码前,我们先来从宏观层面上看一下服务导出逻辑。如下:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
   
    
    // 省略无关代码
    String scope = url.getParameter(Constants.SCOPE_KEY);
    // 如果 scope = none,则什么都不做
    if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
   
        // scope != remote,导出到本地
        if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
   
            exportLocal(url);
        }
        // scope != local,导出到远程
        if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
   
            if (registryURLs != null && !registryURLs.isEmpty()) {
   
                for (URL registryURL : registryURLs) {
   
                  	//省略无关代码
                    
                    // 为服务提供类(ref)生成 Invoker
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值