从源码全面解析 dubbo 注解配置的来龙去脉

本文涉及的产品
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,182元/月
简介: 从源码全面解析 dubbo 注解配置的来龙去脉

从源码解析dubbo的服务注册

一、引言

对于 Java 开发者而言,关于 dubbo ,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。

但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。

本期 dubbo 源码解析系列文章,将带你领略 dubbo 源码的奥秘

本期源码文章吸收了之前 SpringKakfaJUC源码文章的教训,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。

虽然现在是互联网寒冬,但乾坤未定,你我皆是黑马!

废话不多说,发车!

二、环境配置

本篇文章适合对 dubbo 有兴趣 & 日常工作中有使用的人

环境配置:

  • dubbo版本:3.1.8
  • maven版本:3.5.4
  • JDK版本:JDK8
  • Zookeeper版本:3.4.9

因为服务数据是注册在 Zookeeper 上的,所以需要一个 Zookeeper 的可视化界面:ZooInspector

当然,就算你上述环境配置不全,也不影响你本篇文章的阅读体验。

三、dubbo历史及架构

在讲述服务注册之前,我们先聊一聊 dubbo 的历史和架构

1、历史

Dubbo 是一个高性能的、基于 JavaRPC框架。它最初是由阿里巴巴集团开发的,用于支持其大规模的分布式服务架构

以下是Dubbo 的历史:

  • 2011年:Dubbo 项目由阿里巴巴集团内部团队开始开发
  • 2012年:DubboGitHub 上开源发布
  • 2013年:Dubbo 进入 Apache孵化器,成为 Apache 的一个孵化项目
  • 2014年:Dubbo正式成为Apache顶级项目
  • 2016年:Dubbo 被阿里巴巴集团捐赠给了Apache基金会
  • 2018年:Dubbo 发布了 2.7.x 版本,增加了很多新特性和改进
  • 2021年:Dubbo 发布了 2.7.8 版本
  • 2023年:``Dubbo发布了3.2.0` 版本

目前,Dubbo 仍然是业界广泛使用的 RPC 框架之一,得到了众多企业和开发者的支持和认可。

2、架构

dubbo 最经典的还是下面这张架构图,简单描述了 dubbo 整个的运行逻辑

运行流程:

1、服务端启动将服务以 ip:port 的形式注册至注册中心

2、消费端启动时去注册中心一次性订阅所有的注册接口

3、当服务端的接口发生变化时,通知注册中心,由注册中心通知我们的消费端更改接口信息

4、消费端通过负载均衡调用注册接口

5、服务端和消费端调用完成之后,将请求的数据次数通过监控中心上报

四、服务注册

大家初次听到这个名词,可能有点好奇,服务注册是什么鬼?

简单来说:服务端启动将服务信息注册到Zookeeper的过程

假设我们是一个重来没有接触过 dubbo 源码的人,如果你是 dubbo 的创作者,你会怎么去实现?

我提一下我的想法:

  • 第一:dubbo 背靠 Spring 实现的,肯定要和 Spring 有关
  • 第二:dubbo 的引入有两种:注解 和 xml,所以第一步肯定是解析注入
  • 第三:通过 Spring 的机制,将我们前面注入的 BeanDefinition 进行实例化
  • 第四:获取 Zookeeper 的地址,将服务信息注册上去

我们来看看 dubbo 如何实现的?

4.1 服务配置

我们简单配置一个服务:

启动类:

@EnableDubbo
@SpringBootApplication
public class DubboProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(DubboProviderApplication.class);
    }
}

暴露接口和实现类:

public interface IUserService {
    User getUserById(Long id);
}
@DubboService(timeout = 100, version = "1.0.0.dev")
public class UserServiceImpl implements IUserService {
    @Override
    public User getUserById(Long userId) {
        User user = User(2);
        log.info("服务获取用户信息:{}",user);
        return user;
    }
}

4.2 注解扫描

在我们启动的时候,EnableDubbo 会自动进行扫描,其功能主要注解包括:@EnableDubboConfig@DubboComponentScan

4.2.1 EnableDubboConfig
@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {}

这个注册引入了 DubboConfigConfigurationRegistrar.class,我们直接看这个类的实现

public class DubboConfigConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
    // 删减了入参
    public void registerBeanDefinitions() {
        DubboSpringInitializer.initialize(registry);
    }
}
public static void initialize(BeanDefinitionRegistry registry) {
    // 删减了部分不重要代码
    // 初始化我们的dubbo
    initContext(context, registry, beanFactory);
}
private static void initContext(DubboSpringInitContext context, BeanDefinitionRegistry registry,
                                    ConfigurableListableBeanFactory beanFactory) {
    // 注册DubboDeployApplicationListener
    DubboBeanUtils.registerCommonBeans(registry);
}

下面就是我们 EnableDubboConfig 注解注册的一些类信息:

  • ReferenceAnnotationBeanPostProcessor:解析和处理@Reference注解
  • 注意:这个@Reference注解是消费端使用的!!!服务端用不到!!!
  • DubboDeployApplicationListener:注册监听器,初始化 dubbo 服务
  • DubboConfigApplicationListener:加载 dubbo 的配置

我们后面也会聊到

static void registerCommonBeans(BeanDefinitionRegistry registry) {
    // 解析和处理@Service和@Reference注解
    registerInfrastructureBean(registry, ReferenceAnnotationBeanPostProcessor.BEAN_NAME,
        ReferenceAnnotationBeanPostProcessor.class);
    // 注册监听器
    registerInfrastructureBean(registry, DubboDeployApplicationListener.class.getName(), DubboDeployApplicationListener.class);
    //加载配置
    registerInfrastructureBean(registry, DubboConfigApplicationListener.class.getName(), DubboConfigApplicationListener.class);
}
4.2.2 DubboComponentScan

主要注册了 ServiceAnnotationPostProcessor 该类:

  • ServiceAnnotationPostProcessor:扫描对应 @DubobService 形成实例
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 初始化bean
    DubboSpringInitializer.initialize(registry);
    // 获取@EnableDubbo和@DubboComponentScan注解的包路径
    Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
    // 注册一个后置处理器,这个ServiceAnnotationPostProcessor
    // 用于扫描对应@DubobService形成实例
    registerServiceAnnotationPostProcessor(packagesToScan, registry);
}

通过上述的注解,我们知道了以下逻辑关系:

通过我们之前 Spring 的学习,我们应该有一个他们几个类执行的一个大概的逻辑:

  • ServiceAnnotationPostProcessorSpring 后置处理器的扩展,最开始执行的
  • DubboDeployApplicationListenerSpring 容器初始化完成后,发送事件,开始执行
  • DubboConfigApplicationListenerSpring 容器初始化完成后,发送事件,开始执行

那下面我们一起看看这几个类源码如何实现

4.3 服务实例化

通过 ServiceAnnotationPostProcessor 将服务进行实例化,主要由以下几方面:

  • 扫描我们的注解(@DubboService、@Service)配置
  • 先扫描实现类,将实现类封装成 BeanDefinition 注册至 BeanDefinitionMap
  • 通过我们的实现类找到接口类,将接口类也封装成 BeanDefinition 注册至 BeanDefinitionMap

整体流程图如下:

源码解析如下:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry){
    this.registry = registry;
    scanServiceBeans(resolvedPackagesToScan, registry);
}
private void scanServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
    scanned = true;
    // 生成扫描器
    DubboClassPathBeanDefinitionScanner scanner =
            new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
    BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
    scanner.setBeanNameGenerator(beanNameGenerator);
    // 将下面三个注解放到扫描器里面
    // @since 2.7.7 DubboService.class,
    // @since 2.7.0 Service.class,
    // @since 2.7.3 com.alibaba.dubbo.config.annotation.Service.class
    for (Class<? extends Annotation> annotationType : serviceAnnotationTypes) {
        scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
    }
    ScanExcludeFilter scanExcludeFilter = new ScanExcludeFilter();
    scanner.addExcludeFilter(scanExcludeFilter);
    // 遍历所有的路径进行扫描
    for (String packageToScan : packagesToScan) {
        // 1、扫描路径下的注解(DubboService、Service)
        // 2、将注解的配置生成BeanDefinition(相比正常的BeanDefinition,这里添加了一些注解自定义配置)
        // 3、将BeanDefinition注册至BeanDefinitionMap中
        // 注意:这里只能扫描到实现类(UserServiceImpl),没办法扫描接口(IUserService)
        scanner.scan(packageToScan);
        // 这里去扫描接口(IUserService)
        Set<BeanDefinitionHolder> beanDefinitionHolders =
                findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
        // 遍历所有的实现类(UserServiceImpl、...)
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders){
            // 1、根据实现类找到其接口(IUserService)
            // 2、拿到实现类@DubboService的配置(version->1.0.0.dev、timeout->100)
            // 3、获取bean的名称:ServiceBean:com.msb.common.service.IUserService:1.0.0.dev:group
            // 4、初始化IUserService的BeanDefinition(interface/protocol/registry/ref)参数等
            // 5、注册至BeanDefinitionMap中
            // 注意:这里有一个ref参数,指向我们实现类
            processScannedBeanDefinition(beanDefinitionHolder);
 servicePackagesHolder.addScannedClass(beanDefinitionHolder.getBeanDefinition().getBeanClassName());
        }
        servicePackagesHolder.addScannedPackage(packageToScan);
    }
}

我们看到,在我们的接口类里面有一个 ref 方法指向我们的实现类:

简单来理解,当我们消费端去调用接口时,由服务端接口类通过 ref 寻找指定的 实现类

4.4 服务配置初始化

整体流程如下:

通过 DubboConfigApplicationListener 进行服务的配置加载

public void onApplicationEvent(DubboConfigInitEvent event) {
    if (nullSafeEquals(applicationContext, event.getSource())) {
        // CAS只允许一个线程来初始化配置
        if (initialized.compareAndSet(false, true)) {
            initDubboConfigBeans();
        }
    }
}
private void initDubboConfigBeans() {
    applicationContext.getBean(DubboConfigBeanInitializer.BEAN_NAME, DubboConfigBeanInitializer.class);
    // 配置初始化
    moduleModel.getDeployer().prepare();
}
public void prepare() {
    applicationDeployer.initialize();
    this.initialize();
}

这里可以看到分成了两个 initialize,我们分别看一下其实现

4.4.1 applicationDeployer.initialize
  • registerShutdownHook:注册 DubboJVM 关闭钩子,当 JVM 关闭时会自动调用 Dubbo 的销毁方法
  • startConfigCenter:初始化 Dubbo 的配置中心(Zookeeper),从 Dubbo 的配置文件中读取配置中心地址,根据配置初始化 Dubbo 的配置中心
  • loadApplicationConfigs:加载 Dubbo 的配置信息,用于初始化 Dubbo 的各个组件(监控、协议、元数据、指标等)
  • initModuleDeployers:初始化 Dubbo 模块部署器,该方法会根据 Dubbo 配置信息初始化相应的模块部署器,注册上下文
  • startMetadataCenter:方法用于启动 Dubbo 的元数据中心
public void initialize() {
    if (initialized) {
        return;
    }
    // 锁一下
    synchronized (startLock) {
        if (initialized) {
            return;
        }
        // 注册Dubbo应用程序的JVM关闭钩子
        registerShutdownHook();
        // 初始化Dubbo的配置中心
        startConfigCenter();
        // 加载Dubbo应用程序的配置信息
        loadApplicationConfigs();
        // 初始化Dubbo应用程序的模块部署器,
        initModuleDeployers();
        // 启动Dubbo的元数据中心
        startMetadataCenter();
        initialized = true;
    }
}
4.4.2 this.initialize
  • 初始化 Dubbo 模块,并加载 Dubbo 模块的配置信息和元数据信息
  • 读取 Dubbo 模块的 ModuleConfig 配置,并根据配置设置 Dubbo 模块的参数,例如是否异步导出服务、是否异步引用服务、是否在后台线程中启动服务
public void initialize() throws IllegalStateException {
    if (initialized) {
        return;
    }
    synchronized (this) {
        if (initialized) {
            return;
        }
        loadConfigs();
        ModuleConfig moduleConfig = moduleModel.getConfigManager().getModule().orElseThrow(() -> new IllegalStateException("Default module config is not initialized"));
        exportAsync = Boolean.TRUE.equals(moduleConfig.getExportAsync());
        referAsync = Boolean.TRUE.equals(moduleConfig.getReferAsync());
        background = moduleConfig.getBackground();
        if (background == null) {
            background = isExportBackground() || isReferBackground();
        }
        initialized = true;
        if (logger.isInfoEnabled()) {
            logger.info(getIdentifier() + " has been initialized!");
        }
    }
}

4.4 服务注册

我们本篇简单的介绍下 服务注册 的过程,具体细节下一篇文章我们细聊

五、总结

鲁迅先生曾说:独行难,众行易,和志同道合的人一起进步。彼此毫无保留的分享经验,才是对抗互联网寒冬的最佳选择。

其实很多时候,并不是我们不够努力,很可能就是自己努力的方向不对,如果有一个人能稍微指点你一下,你真的可能会少走几年弯路。


相关文章
|
8月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
724 29
|
8月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
218 4
|
8月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
8月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
8月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
12月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
285 2
|
8月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
9月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
1739 1
|
11月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
11月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析

推荐镜像

更多
  • DNS
  • 下一篇
    开通oss服务