当前位置: 首页 > article >正文

ROS2软件调用架构和机制解析:Publisher创建

术语

  • DDS (Data Distribution Service): 用于实时系统的数据分发服务标准,是ROS 2底层通信的基础
  • RMW (ROS Middleware): ROS中间件接口,提供与具体DDS实现无关的抽象API
  • QoS (Quality of Service): 服务质量策略,控制通信的可靠性、历史记录、耐久性等属性
  • 符号解析: 动态库加载过程中,查找和绑定函数指针的机制

1. 架构概述

ROS2采用分层设计,通过多层抽象实现高度灵活性和可插拔性。其调用链如下:

应用层(开发人员) → rclcpp(C++客户端库) → rcl(C语言客户端库) → rmw(中间件接口) → rmw_implementation(动态加载层) → 具体DDS实现

2. 发布者创建流程详解

2.1 用户代码层(rclcpp::Node::create_publisher)

通常使用以下代码创建发布者:

auto node = std::make_shared<rclcpp::Node>("publisher_node");
auto publisher = node->create_publisher<std_msgs::msg::String>("topic_name", 10);  // 队列深度为10

2.2 rclcpp层(Node类)

node.hpp文件中对各模板函数进行声明,函数的具体实现在node_impl.hpp文件中进行定义。

class Node : public std::enable_shared_from_this<Node>
{template<typename MessageT,typename AllocatorT = std::allocator<void>,typename PublisherT = rclcpp::Publisher<MessageT, AllocatorT>>std::shared_ptr<PublisherT>create_publisher(const std::string & topic_name,const rclcpp::QoS & qos,const PublisherOptionsWithAllocator<AllocatorT> & options =PublisherOptionsWithAllocator<AllocatorT>());
}

node_impl.hpp文件中,node->create_publisher 调用一系列rclcpp函数:

template<typename MessageT, typename AllocatorT, typename PublisherT>
std::shared_ptr<PublisherT>
Node::create_publisher(const std::string & topic_name,const rclcpp::QoS & qos,const PublisherOptionsWithAllocator<AllocatorT> & options)
{return rclcpp::create_publisher<MessageT, AllocatorT, PublisherT>(*this,extend_name_with_sub_namespace(topic_name, this->get_sub_namespace()),qos,options);
}

该方法调用create_publisher.hpp文件的node_topics_interface->create_publisher创建类型特定的工厂

template<typename MessageT,typename AllocatorT = std::allocator<void>,typename PublisherT = rclcpp::Publisher<MessageT, AllocatorT>,typename NodeParametersT,typename NodeTopicsT>
std::shared_ptr<PublisherT>
create_publisher(NodeParametersT & node_parameters,NodeTopicsT & node_topics,const std::string & topic_name,const rclcpp::QoS & qos,const rclcpp::PublisherOptionsWithAllocator<AllocatorT> & options = (rclcpp::PublisherOptionsWithAllocator<AllocatorT>())
)
{auto node_topics_interface = rclcpp::node_interfaces::get_node_topics_interface(node_topics);const rclcpp::QoS & actual_qos = options.qos_overriding_options.get_policy_kinds().size() ?rclcpp::detail::declare_qos_parameters(options.qos_overriding_options, node_parameters,node_topics_interface->resolve_topic_name(topic_name),qos, rclcpp::detail::PublisherQosParametersTraits{}) :qos;// Create the publisher.auto pub = node_topics_interface->create_publisher(topic_name,rclcpp::create_publisher_factory<MessageT, AllocatorT, PublisherT>(options),actual_qos);// Add the publisher to the node topics interface.node_topics_interface->add_publisher(pub, options.callback_group);return std::dynamic_pointer_cast<PublisherT>(pub);
}

node_topics_interface->create_publisher实际调用node_topics.cpp文件的NodeTopics::create_publisher非模板方法,该方法使用publisher_factory.hpp文件中工厂的create_typed_publisher函数创建实际的发布者,创建的发布者进行后期初始化设置。

rclcpp::PublisherBase::SharedPtr
NodeTopics::create_publisher(const std::string & topic_name,const rclcpp::PublisherFactory & publisher_factory,const rclcpp::QoS & qos)
{// Create the MessageT specific Publisher using the factory, but return it as PublisherBase.return publisher_factory.create_typed_publisher(node_base_, topic_name, qos);
}

create_typed_publisher的实现如下:

template<typename MessageT, typename AllocatorT, typename PublisherT>
PublisherFactory
create_publisher_factory(const rclcpp::PublisherOptionsWithAllocator<AllocatorT> & options)
{PublisherFactory factory {// factory function that creates a MessageT specific PublisherT[options](rclcpp::node_interfaces::NodeBaseInterface * node_base,const std::string & topic_name,const rclcpp::QoS & qos) -> std::shared_ptr<PublisherT>{auto publisher = std::make_shared<PublisherT>(node_base, topic_name, qos, options);// This is used for setting up things like intra process comms which// require this->shared_from_this() which cannot be called from// the constructor.publisher->post_init_setup(node_base, topic_name, qos, options);return publisher;}};// return the factory now that it is populatedreturn factory;
}

创建特定的Publisher时,publisher.hpp文件内部会转到具体的Publisher构造函数:

Publisher(rclcpp::node_interfaces::NodeBaseInterface * node_base,const std::string & topic,const rclcpp::QoS & qos,const rclcpp::PublisherOptionsWithAllocator<AllocatorT> & options)
: PublisherBase(node_base,topic,rclcpp::get_message_type_support_handle<MessageT>(),options.template to_rcl_publisher_options<MessageT>(qos)),options_(options),published_type_allocator_(*options.get_allocator()),ros_message_type_allocator_(*options.get_allocator())
{// 初始化内存分配器和事件回调
}

PublisherBase构造函数则进行RCL层的publisher初始化

PublisherBase::PublisherBase(rclcpp::node_interfaces::NodeBaseInterface * node_base,const std::string & topic,const rosidl_message_type_support_t & type_support,const rcl_publisher_options_t & publisher_options)
: node_base_(node_base),topic_(topic)
{// 创建rcl_publisher_t实例publisher_handle_ = std::shared_ptr<rcl_publisher_t>(new rcl_publisher_t, [node_handle](rcl_publisher_t * publisher) {if (rcl_publisher_fini(publisher, node_handle) != RCL_RET_OK) {RCLCPP_ERROR(/* ... */);}delete publisher;});*publisher_handle_.get() = rcl_get_zero_initialized_publisher();// 关键调用:初始化RCL层publisherrcl_ret_t ret = rcl_publisher_init(publisher_handle_.get(),node_base->get_rcl_node_handle(),&type_support,topic.c_str(),&publisher_options);if (ret != RCL_RET_OK) {// 错误处理}
}

2.3 rcl层

rcl_publisher_init 函数进一步处理:

rcl_ret_t
rcl_publisher_init(rcl_publisher_t * publisher,const rcl_node_t * node,const rosidl_message_type_support_t * type_support,const char * topic_name,const rcl_publisher_options_t * options
)
{// 参数验证和初始化// 向RMW层请求创建发布者// rmw_handle为rmw_publisher_t *类型publisher->impl->rmw_handle = rmw_create_publisher(rcl_node_get_rmw_handle(node),type_support,remapped_topic_name,&(options->qos),&(options->rmw_publisher_options));// 错误处理与返回值设置
}

2.4 rmw层(通过rmw_implementation)

此处进入rmw_implementation包的functions.cpp中,其中rmw_create_publisherrmw.h文件中定义的接口函数:

RMW_INTERFACE_FN(rmw_create_publisher,rmw_publisher_t *, nullptr,5, ARG_TYPES(const rmw_node_t *, const rosidl_message_type_support_t *, const char *,const rmw_qos_profile_t *, const rmw_publisher_options_t *))

这个宏展开后生成:

#define RMW_INTERFACE_FN(name, ReturnType, error_value, _NR, ...) \void * symbol_ ## name = nullptr; \ReturnType name(EXPAND(ARGS_ ## _NR(__VA_ARGS__))) \{ \CALL_SYMBOL( \name, ReturnType, error_value, ARG_TYPES(__VA_ARGS__), \EXPAND(ARG_VALUES_ ## _NR(__VA_ARGS__))); \}

CALL_SYMBOL宏进一步展开:

#define CALL_SYMBOL(symbol_name, ReturnType, error_value, ArgTypes, arg_values) \if (!symbol_ ## symbol_name) { \/* only necessary for functions called before rmw_init */ \//获取库中的函数符号symbol_ ## symbol_name = get_symbol(#symbol_name); \} \if (!symbol_ ## symbol_name) { \/* error message set by get_symbol() */ \return error_value; \} \typedef ReturnType (* FunctionSignature)(ArgTypes); \// 根据函数地址,调用加载的函数FunctionSignature func = reinterpret_cast<FunctionSignature>(symbol_ ## symbol_name); \return func(arg_values);

get_symbol 函数获取函数符号:

void *
get_symbol(const char * symbol_name)
{try {return lookup_symbol(get_library(), symbol_name);} catch (const std::exception & e) {RMW_SET_ERROR_MSG_WITH_FORMAT_STRING("failed to get symbol '%s' due to %s",symbol_name, e.what());return nullptr;}
}

2.5 加载和符号查找

关键的lookup_symbol函数负责从已加载的库中查找符号:

void *
lookup_symbol(std::shared_ptr<rcpputils::SharedLibrary> lib, const std::string & symbol_name)
{if (!lib) {if (!rmw_error_is_set()) {RMW_SET_ERROR_MSG("no shared library to lookup");}  // else assume library loading failedreturn nullptr;}if (!lib->has_symbol(symbol_name)) {try {std::string library_path = lib->get_library_path();RMW_SET_ERROR_MSG_WITH_FORMAT_STRING("failed to resolve symbol '%s' in shared library '%s'",symbol_name.c_str(), library_path.c_str());} catch (const std::exception & e) {RMW_SET_ERROR_MSG_WITH_FORMAT_STRING("failed to resolve symbol '%s' in shared library due to %s",symbol_name.c_str(), e.what());}return nullptr;}// 返回找到的函数指针return lib->get_symbol(symbol_name);
}

get_library()函数负责加载RMW实现库:

std::shared_ptr<rcpputils::SharedLibrary> get_library()
{if (!g_rmw_lib) {// 懒加载策略 - 首次使用时加载g_rmw_lib = load_library();}return g_rmw_lib;
}

2.6 具体DDS实现

rmw_publisher.cpp文件中,调用rmw_fastrtps_cpp::create_publisher函数进行publisher创建。最终,通过符号解析得到的函数指针指向特定DDS实现(如FastDDS、CycloneDDS)提供的具体rmw_create_publisher实现:

// 在FastDDS中的实现示例
rmw_publisher_t *
rmw_fastrtps_cpp::create_publisher(const CustomParticipantInfo * participant_info,const rosidl_message_type_support_t * type_supports,const char * topic_name,const rmw_qos_profile_t * qos_policies,const rmw_publisher_options_t * publisher_options,bool keyed,bool create_publisher_listener)
{rmw_publisher_t * publisher = rmw_fastrtps_cpp::create_publisher(participant_info,type_supports,topic_name,qos_policies,publisher_options,false,true);// 创建并返回rmw_publisher_t结构
}rmw_publisher_t *
rmw_fastrtps_cpp::create_publisher(const CustomParticipantInfo * participant_info,const rosidl_message_type_support_t * type_supports,const char * topic_name,const rmw_qos_profile_t * qos_policies,const rmw_publisher_options_t * publisher_options,bool keyed,bool create_publisher_listener)
{eprosima::fastdds::dds::Publisher * publisher = participant_info->publisher_;...
}

3. 层级交互总结

DDS层
实现层
动态加载层
中间件接口层
客户端库层
用户应用层
环境变量决定
环境变量决定
环境变量决定
环境变量决定
eProsima FastDDS
Eclipse CycloneDDS
RTI Connext DDS
rmw_fastrtps_shared_cpp
FastDDS共享实现
rmw_fastrtps_cpp
FastDDS C++实现
rmw_fastrtps_dynamic_cpp
FastDDS动态类型支持
rmw_cyclonedds_cpp
CycloneDDS实现
rmw_connextdds
Connext DDS实现
rmw_implementation
动态加载机制
根据RMW_IMPLEMENTATION
环境变量加载具体实现
rmw
定义中间件接口API
所有RMW实现必须遵循的
接口规范
rmw_dds_common
通用DDS功能
为所有基于DDS的
RMW实现提供共享功能
rclcpp层
C++客户端库
提供面向对象API
管理C++对象生命周期
rcl层
C语言客户端库
处理资源分配
错误处理和参数验证
用户代码
node->create_publisher(...)
  1. rclcpp层:提供面向对象的友好API,管理C++对象生命周期
  2. rcl层:提供C语言API,处理资源分配、错误处理和参数验证
  3. rmw层:定义中间件抽象接口,与具体DDS无关
  4. rmw_implementation层
    • 使用环境变量决定加载哪个RMW实现
    • 通过动态符号查找机制(lookup_symbol)获取函数指针
    • 通过函数指针转发调用到实际DDS实现
  5. 具体DDS实现:执行真正的DDS操作,与网络通信

4. 实际应用示例

在ROS 2系统中,可以通过环境变量轻松切换底层DDS实现:

# 使用FastDDS (默认)
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp# 使用CycloneDDS
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp# 使用Connext DDS
export RMW_IMPLEMENTATION=rmw_connextdds# 查看当前使用的RMW实现
ros2 doctor --report | grep middleware

相关文章:

ROS2软件调用架构和机制解析:Publisher创建

术语 DDS (Data Distribution Service): 用于实时系统的数据分发服务标准&#xff0c;是ROS 2底层通信的基础RMW (ROS Middleware): ROS中间件接口&#xff0c;提供与具体DDS实现无关的抽象APIQoS (Quality of Service): 服务质量策略&#xff0c;控制通信的可靠性、历史记录、…...

Android -- 使用Sharepreference保存List储存失败,原因是包含Bitmap,drawable等类型数据

1.报错信息如下&#xff1a; class android.content.res.ColorStateList declares multiple JSON fields named mChangingConfigurations 2.Bean类属性如下&#xff1a; data class AppInfoBean( val appName: String?, val appIcon: Drawable, val appPackage: String?,…...

java后端开发day23--面向对象进阶(四)--抽象类、接口、内部类

&#xff08;以下内容全部来自上述课程&#xff09; 1.抽象类 父类定义抽象方法后&#xff0c;子类的方法就必须重写&#xff0c;抽象方法在的类就是抽象类。 1.定义 抽象方法 将共性的行为&#xff08;方法&#xff09;抽取到父类之后。由于每一个子类执行的内容是不一样…...

Go - 泛型的使用

泛型的语法 泛型为Go语言添加了三个新的重要特性: 函数和类型的类型参数。将接口类型定义为类型集&#xff0c;包括没有方法的类型。类型推断&#xff0c;它允许在调用函数时在许多情况下省略类型参数。 类型参数 类型参数的使用 除了函数中支持类型参数列表外&#xff0c…...

蓝桥杯刷题-dp-线性dp(守望者的逃离,摆花,线段)

[NOIP 2007 普及组] 守望者的逃离 题目描述 恶魔猎手尤迪安野心勃勃&#xff0c;他背叛了暗夜精灵&#xff0c;率领深藏在海底的娜迦族企图叛变。 守望者在与尤迪安的交锋中遭遇了围杀&#xff0c;被困在一个荒芜的大岛上。 为了杀死守望者&#xff0c;尤迪安开始对这个荒岛…...

内容中台的企业CMS架构是什么?

企业CMS模块化架构 现代企业内容管理系统的核心在于模块化架构设计&#xff0c;通过解耦内容生产、存储、发布等环节构建灵活的技术栈。动态/静态发布引擎整合技术使系统既能处理实时更新的产品文档&#xff0c;也能生成高并发的营销落地页&#xff0c;配合版本控制机制确保内…...

算法题(81):询问学号

审题&#xff1a; 需要我们根据给出的n值确定录入数据个数&#xff0c;然后根据给出的数据存储学号。再根据m值确定需要输出的学号个数&#xff0c;然后根据数组内容输出学号 思路: 我们可以利用数组进行数据顺序存储&#xff0c;以及随机读取完成本题 由于学号最大为1e9&#…...

React antd的datePicker自定义,封装成组件

一、antd的datePicker自定义 需求&#xff1a;用户需要为日期选择器的每个日期单元格添加一个Tooltip&#xff0c;当鼠标悬停时显示日期、可兑换流量余额和本公会可兑流量。这些数据需要从接口获取。我需要结合之前的代码&#xff0c;确保Tooltip正确显示&#xff0c;并且数据…...

C++ AVL树详解(含模拟实现)

目录 AVL树的概念 AVL树节点的定义 AVL树的插入 AVL树的旋转&#xff08;难点&#xff09; AVL树的验证 AVL树的删除(本文不做具体的模拟实现) AVL树的性能 AVL树的模拟实现 AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索…...

Spring Boot 3.x 系列【3】Spring Initializr快速创建Spring Boot项目

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Spring Boot版本3.0.3 源码地址&#xff1a;https://gitee.com/pearl-organization/study-spring-boot3 文章目录 前言安装JDK 17创建Spring Boot 项目 方式1&#xff1a;网页在线生成方式2&#…...

Elasticsearch:过滤 HNSW 搜索,快速模式

作者&#xff1a;来自 Elastic Benjamin Trent 通过我们的 ACORN-1 算法实现&#xff0c;探索我们对 Apache Lucene 中的 HNSW 向量搜索所做的改进。 多年来&#xff0c;Apache Lucene 和 Elasticsearch 一直支持使用 kNN 查询的过滤搜索&#xff0c;允许用户检索符合指定元数据…...

TCP长连接与短连接

TCP长连接与短连接 TCP&#xff08;传输控制协议&#xff09;中的长连接和短连接是两种不同的连接管理方式&#xff0c;各有优缺点&#xff1a; 短连接 短连接是指客户端与服务器完成一次数据交换后就断开连接。下次需要通信时&#xff0c;再重新建立连接。 特点&#xff1…...

【AI测试学习】AnythingLLM+Ollama+DeepSeek部署私人知识库

1.搭建DeepSeek大语言模型 1.1Ollama大预言模型部署 Ollama简化了大型语言模型的运行,让每个人都能在本地轻松体验AI的强大,打开浏览器-下载Ollama-输入命令-搞定,这是本地部署大语言模型的全新方式。 这里我们借助Ollama大预言模型部署工具进行搭建 官网如下:Ollama …...

防流、节抖、重绘、回流原理,以及实现方法和区别

防流、节抖、重绘、回流原理&#xff0c;以及实现方法和区别&#xff0c;还有就是为什么会出现这种情况&#xff1f; 防抖&#xff08;Debounce&#xff09; 原理 防抖就像是你坐电梯&#xff0c;如果你一直不停地按开门按钮&#xff0c;电梯不会每次都开门&#xff0c;而是…...

通义灵码插件安装入门教学 - IDEA(安装篇)

在开发过程中&#xff0c;使用合适的工具和插件可以极大地提高我们的工作效率。今天&#xff0c;我们将详细介绍如何在 IntelliJ IDEA 中安装并配置通义灵码插件&#xff0c;这是一款旨在提升开发者效率的实用工具。无论你是新手还是有经验的开发者&#xff0c;本文都将为你提供…...

ES、OAS、ERP、电子政务、企业信息化(高软35)

系列文章目录 ES、OAS、ERP、电子政务、企业信息化 文章目录 系列文章目录前言一、专家系统&#xff08;ES&#xff09;二、办公自动化系统&#xff08;OAS&#xff09;三、企业资源规划&#xff08;ERP&#xff09;四、典型信息系统架构模型1.政府信息化和电子政务2.企业信息…...

用大白话解释缓存Redis +MongoDB是什么有什么用怎么用

Redis和MongoDB是什么&#xff1f; Redis&#xff1a;像你家的“小冰箱”&#xff0c;专门存高频使用的食物&#xff08;数据&#xff09;。它是基于内存的键值数据库&#xff0c;读写速度极快&#xff08;每秒超10万次操作&#xff09;。比如你每次打开手机App&#xff0c;用…...

华为数通Datacom认证体系详解:从HCIA到HCIE的进阶路径

华为数通Datacom&#xff08;Data Communication&#xff09;课程是华为认证体系中的核心方向之一&#xff0c;聚焦企业网络通信与数据通信技术&#xff0c;适合从事网络规划、部署和运维的人员。 一、数通Datacom课程体系 华为数通Datacom认证分为 三个级别&#xff0c;逐级递…...

PyTorch 的 nn.NLLLoss:负对数似然损失全解析

PyTorch 的 nn.NLLLoss&#xff1a;负对数似然损失全解析 在 PyTorch 的损失函数家族中&#xff0c;nn.NLLLoss&#xff08;Negative Log Likelihood Loss&#xff0c;负对数似然损失&#xff09;是一个不太起眼但非常重要的成员。它经常跟 LogSoftmax 搭配出现&#xff0c;尤…...

《数据安全架构设计与实战》的目录

《数据安全架构设计与实战》的目录如下1&#xff1a; 第一部分 安全架构基础 第 1 章 架构第 2 章 安全架构 2.1 什么是安全2.2 为什么使用 “数据安全” 这个术语2.3 什么是安全架构2.4 安全架构 5A 方法论2.5 安全架构 5A 与 CIA 的关系 第二部分 产品安全架构 …...

python-leetcode-删除并获得点数

740. 删除并获得点数 - 力扣&#xff08;LeetCode&#xff09; 解法 1&#xff1a;动态规划&#xff08;O(n) 时间&#xff0c;O(n) 空间&#xff09; class Solution:def deleteAndEarn(self, nums: List[int]) -> int:if not nums:return 0# 统计每个数的贡献points Cou…...

助力DeepSeek私有化部署服务:让企业AI落地更简单、更安全

在数字化转型的浪潮中&#xff0c;越来越多的企业选择私有化部署AI技术&#xff0c;以保障数据安全、提升业务效率并实现自主可控。DeepSeek作为行业领先的AI开源技术&#xff0c;其技术可以支持企业私有化部署&#xff0c;企业需要一站式服务私有化部署&#xff0c;涵盖硬件采…...

Android JNI开发指南

了解Android JNI开发。首先&#xff0c;我需要回忆一下JNI的基本概念和作用。JNI允许Java代码与本地代码&#xff08;如C/C&#xff09;交互&#xff0c;这在需要高性能或者使用现有C/C库时非常有用。 接下来&#xff0c;我应该整理开发步骤。用户可能想知道如何开始&#xff…...

【每天认识一个漏洞】url重定向

&#x1f31d;博客主页&#xff1a;菜鸟小羊 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 常见应用场景 主要是业务逻辑中需要进行跳转的地方。比如登录处、注册处、访问用户信息、订单信息、加入购物车、分享、收…...

纯代码实战--用Deepseek+SQLite+Ollama搭建数据库助手

如何用Python调用本地模型实现DeepSeek提示词模板&#xff1a;一步步教你高效解决13种应用场景 从零到一&#xff1a;纯代码联合PyQt5、Ollama、Deepseek打造简易版智能聊天助手 用外接知识库武装大模型&#xff1a;基于Deepseek、Ollama、LangChain的RAG实战解析 纯代码实战–…...

2025 最新版鸿蒙 HarmonyOS 开发工具安装使用指南

为保证 DevEco Studio 正常运行&#xff0c;建议电脑配置满足如下要求&#xff1a; Windows 系统 操作系统&#xff1a;Windows10 64 位、Windows11 64 位内存&#xff1a;16GB 及以上硬盘&#xff1a;100GB 及以上分辨率&#xff1a;1280*800 像素及以上 macOS 系统 操作系统…...

日期时间 API

日期时间 API (java.time 包)&#xff0c;旨在解决旧版 java.util.Date 和 java.util.Calendar 存在的一些设计缺陷&#xff0c;比如线程不安全、时区处理不一致等问题。新 API 基于 ISO 8601 标准&#xff0c;更加直观、简洁&#xff0c;且支持时区和区域设置。主要类有&#…...

AI数字人开发,引领科技新潮流

引言 随着人工智能技术的迅猛发展&#xff0c;AI 数字人在影视娱乐、客户服务、教育及医疗等多个领域展现出巨大的潜力。本文旨在为开发者提供一份详细的 AI 数字人系统开发指南&#xff0c;涵盖从基础架构到实现细节的各个方面&#xff0c;包括人物建模、动作生成、语音交互、…...

领域驱动设计:事件溯源架构简介

概述 事件溯源架构通常由3种应用设计模式组成,分别是:事件驱动(Event Driven),事件溯源(Event Source)、CQRS(读写分离)。这三种应用设计模式常见于领域驱动设计(DDD)中,但它们本身是一种应用设计的思想,不仅仅局限于DDD,每一种模式都可以单独拿出来使用。 E…...

自定义类加载器国密版本冲突

自定义类加载器国密版本冲突 对接三方接口经常使用到国密加密包&#xff08;bcprov&#xff09;&#xff0c;此时系统已经引入了1.5版本&#xff0c;而三方提供的sdk中引用了1.6版版本&#xff0c;两个版本有冲突&#xff0c;如果系统加载到1.5版本的将会加密异常(各种奇怪的异…...