AutoCompose - 携程自动编排原理 -【编排关系DAG的构建】

news2025/5/25 18:43:38

AutoCompose - 携程自动编排原理 -【编排关系DAG的构建】

  • 前言
  • 一. Spring / SpringBoot 的兼容
    • ✅ spring.factories 文件
      • 🧩 特点
      • 📄 示例
    • ✅ META-INF/spring/ 目录下的文件(Spring Boot 2.4+ 新特性)
      • 🧩 特点
      • 📄 示例
    • ✅ 总结对比
  • 二. 核心类结构设计 dependency 模块
    • ① 模块设计目标
    • ② 核心流程详解
      • AutoComposableDependenciesCache
      • AutoComposableDependenciesCacheInitializeListener
      • DependentAutoComposableBeanUtil
    • ③ 生成可视化流程图(PNG)
  • 三. 总结

前言

前序文章:AutoCompose - 携程自动编排框架的简单介绍

AutoCompose 的实现核心主要依赖这几个方面:

  • 编排结果的存储
  • 编排关系DAG的构建
  • 编排实现方式

那本篇文章就继续讲编排实现的第二个核心:编排关系DAG的构建原理。

框架是建立在SpringBean的基础上来实现的,在项目启动的时候,就能够根据编排类(实现了AutoComposable 接口的类)之间的注入关系,构建出编排流程。

所以在讲核心原理之前,我们先看看怎么让这个框架,自适应 Spring / SpringBoot

一. Spring / SpringBoot 的兼容

在 Spring Boot 项目中,spring.factoriesMETA-INF/spring/ 目录下的文件都用于自动装配机制的配置:


✅ spring.factories 文件

  • 📍 路径:src/main/resources/META-INF/spring.factories
  • 📌 功能:Spring Boot 的 SPI(Service Provider Interface)机制实现, 主要用于定义 自动装配类、监听器、初始化器等扩展点
  • 🔧 常见用途
类型示例
自动配置类org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.MyAutoConfiguration
应用监听器org.springframework.context.ApplicationListener=com.example.MyApplicationListener
初始化器org.springframework.context.ApplicationContextInitializer=com.example.MyContextInitializer

🧩 特点

  • 使用 Java Properties 格式
  • 支持多行写法(\ 续行符)。
  • SpringFactoriesLoader 加载,是 Spring Boot 最早支持的自动装配机制。

📄 示例

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.MyAutoConfiguration

org.springframework.context.ApplicationListener=\
  com.example.MyApplicationListener

✅ META-INF/spring/ 目录下的文件(Spring Boot 2.4+ 新特性)

  • 📍 路径示例:src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.xxx.imports

  • 📌 功能:Spring Boot 2.4 引入的新自动装配机制, 使用 模块化自动装配配置方式是对 spring.factories 的改进和替代,提升加载性能与可维护性每个自动配置接口对应一个独立文件,避免多个组件配置混杂

🧩 特点

  • 每个自动装配入口单独一个文件
  • 文件名格式:全限定接口名.imports
  • 文件内容为每行一个自动配置类名
  • 更容易被工具处理和优化(如 AOT 编译、GraalVM Native Image)

📄 示例

com.example.MyAutoConfiguration
com.example.MyApplicationListener

✅ 总结对比

对比项spring.factoriesMETA-INF/spring/xxx.imports
引入版本Spring Boot 1.xSpring Boot 2.4+
存放路径[META-INF/spring.factories](file:///Users/jj.lin/Desktop/Project/auto-compose/auto-compose-core/src/main/resources/META-INF/spring.factories)META-INF/spring/<接口名>.imports
文件结构单一文件包含所有类型配置多个文件,按接口分类
配置格式Java Properties,支持多行纯文本,每行一个类名
可读性较差,配置混杂更清晰,配置分离
工具支持不易解析易于构建工具处理(AOT/GraalVM)
兼容性完全兼容旧版Spring Boot 2.4+ 开始支持

本框架两种实现方式都包含了,在后文会介绍。

二. 核心类结构设计 dependency 模块

dependency 模块是整个自动编排框架中构建组件依赖关系(DAG)的核心部分。我们来详细介绍它的实现逻辑。

① 模块设计目标

该模块的设计目标是:

在应用启动完成后,自动解析并缓存所有实现了 AutoComposable 接口的组件之间的依赖关系,形成一个 DAG(Directed Acyclic Graph),供后续执行调度使用

同时:

  • 支持生成可视化的依赖图(PNG)
  • 支持生成文本格式的依赖树
  • 支持运行时查看组件依赖路径
  • 抛出异常提醒开发者避免循环依赖

核心结构分为三个类

类名职责
AutoComposableDependenciesCache缓存组件间依赖关系,支持生成字符串和图片
AutoComposableDependenciesCacheInitializeListenerSpring 启动后初始化依赖缓存
DependentAutoComposableBeanUtil(内部工具类)扫描组件依赖的 Bean,并递归处理

② 核心流程详解

AutoComposableDependenciesCache

🧠 什么是 DAG?

  • DAG = Directed Acyclic Graph(有向无环图)
  • 在本框架中:
    • 每个组件是一个节点
    • 依赖关系是有向边
    • 循环依赖会被检测并抛异常

我们先说下 AutoComposableDependenciesCache 类,其中关键数据结构:

  1. Map<Class<? extends AutoComposable<T, R>>, Set<? extends AutoComposable<T, R>>> cacheMap

这是 DAG 的基础存储结构:

private Map<Class<? extends AutoComposable<T, R>>, Set<? extends AutoComposable<T, R>>> cacheMap;

📌 存储结构说明:

KeyValue
组件类 Class 对象 (实现了 AutoComposable 接口)该组件直接依赖的其他组件(实现了 AutoComposable 接口)集合

例如:

FlightSearchComponent.class -> [FlightInfoComponent.class, FlightPricingComponent.class]

AutoComposableDependenciesCacheInitializeListener

AutoComposableDependenciesCacheInitializeListener 这个类是一个 SpringApplicationListener<ApplicationReadyEvent>,它的主要作用:

在 Spring Boot 启动完成之后,自动执行一次 DAG 构建操作

✅ 核心步骤:重写 onApplicationEvent 函数

public class AutoComposableDependenciesCacheInitializeListener implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {}
}
  1. 获取所有AutoComposable实现类

    Map<String, AutoComposable> autoComposableBeanMap =
            event.getApplicationContext().getBeansOfType(AutoComposable.class);
    
  2. 过滤掉代理 Bean(ScopedTarget)

    autoComposableBeanMap = autoComposableBeanMap.entrySet().stream()
            .filter(entry -> !ScopedProxyUtils.isScopedTarget(entry.getKey()))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    
  3. 初始化 DAG 缓存 map

    AutoComposableDependenciesCache.INSTANCE.initCacheMap(autoComposableBeanMap.size());
    
  4. 遍历每个组件,构建其依赖关系

    autoComposableBeanMap.forEach((key, value) -> {
        AutoComposableDependenciesCache.INSTANCE.cacheDependentBeans(
                AopUtils.getTargetClass(value),
                dependentAutoComposableBeanUtil.getBeans(key));
    });
    

DependentAutoComposableBeanUtil

DependentAutoComposableBeanUtil。这是一个内部工具类,职责是为每个 AutoComposable 类型的 Bean 递归扫描并收集其直接或间接依赖的其他 AutoComposable 组件

类结构概览:

private static class DependentAutoComposableBeanUtil {

    private final ConfigurableApplicationContext applicationContext;
    private final Map<String, AutoComposable> autoComposableBeanMap;

    DependentAutoComposableBeanUtil(ConfigurableApplicationContext applicationContext,
                                    Map<String, AutoComposable> autoComposableBeanMap) {
        this.applicationContext = applicationContext;
        this.autoComposableBeanMap = autoComposableBeanMap;
    }

    Set<AutoComposable> getBeans(String beanName) {
        Set<AutoComposable> beans = new HashSet<>();
        scan(beanName, new HashSet<>(), beans);
        return beans;
    }

    private void scan(String beanName, Set<String> scannedBeanNames, Set<AutoComposable> beans) { ... }

    private String[] getDependenciesForBean(String beanName) { ... }
}

✅ 主要功能:

方法功能
getBeans(String beanName)获取某个组件所依赖的所有 AutoComposable Bean 集合
scan(...)递归扫描依赖链,避免循环依赖
getDependenciesForBean(...)调用 Spring 的 API 获取该 Bean 所依赖的其他 BeanName 列表

核心方法详解

构造函数:注入上下文

DependentAutoComposableBeanUtil(ConfigurableApplicationContext applicationContext,
                                Map<String, AutoComposable> autoComposableBeanMap) {
    this.applicationContext = applicationContext;
    this.autoComposableBeanMap = autoComposableBeanMap;
}

参数说明:

  • applicationContextSpring 容器上下文,用来获取 Bean 及其依赖信息
  • autoComposableBeanMap:项目中所有的 AutoComposable 实现类集合

入口方法:getBeans

Set<AutoComposable> getBeans(String beanName) {
    Set<AutoComposable> beans = new HashSet<>();
    scan(beanName, new HashSet<>(), beans);
    return beans;
}

📌 功能说明:

  • 根据传入的 beanName,开始递归扫描它的依赖
  • 结束后返回一个 Set<AutoComposable>,即当前组件直接或间接依赖的所有实现了 AutoComposable 接口的 Bean

递归扫描方法:scan(...)

private void scan(String beanName,
                  Set<String> scannedBeanNames,
                  Set<AutoComposable> beans) {
    if (scannedBeanNames.contains(beanName)) {
        return; // 已扫描过,避免死循环
    }
    scannedBeanNames.add(beanName);

    for (String dependencyBeanName : getDependenciesForBean(beanName)) {
        if (autoComposableBeanMap.containsKey(dependencyBeanName)) {
            beans.add(autoComposableBeanMap.get(dependencyBeanName));
            continue;
        }
        scan(dependencyBeanName, scannedBeanNames, beans); // 递归扫描
    }
}

🔍 流程说明:

  1. 如果当前 beanName 已经扫描过 → 停止递归(防循环)
  2. 否则标记已扫描,继续处理其依赖项
  3. 遍历 Spring 返回的依赖 BeanName
    • 若是另一个 AutoComposable 实现类 → 添加到结果集
    • 若不是 → 递归扫描该 Bean 的依赖
  4. 最终返回完整的依赖集合

获取 Spring 依赖:getDependenciesForBean(...)

private String[] getDependenciesForBean(String beanName) {
    if (!ScopedProxyUtils.isScopedTarget(beanName)) {
        String scopedTargetBeanName = ScopedProxyUtils.getTargetBeanName(beanName);
        if (applicationContext.containsBean(scopedTargetBeanName)) {
            beanName = scopedTargetBeanName;
        }
    }

    if (!applicationContext.containsBean(beanName)) {
        return new String[0];
    }

    applicationContext.getBean(beanName);
    return applicationContext.getBeanFactory().getDependenciesForBean(beanName);
}

📌 功能说明:

  1. 处理 ScopedTarget 代理 Bean
    • 某些组件可能被 AOP 代理,这里尝试获取真实 Class 对应的 BeanName
  2. 调用 Spring 的 getDependenciesForBean() 方法
    • 这是 Spring 提供的标准 API,可获取指定 BeanName 的依赖列表
  3. 返回当前 Bean 直接依赖的 BeanNames 数组

示例说明

假设你的 Spring 容器中有以下三个组件:

class ComponentA implements AutoComposable<...> {
	@Autowired
    private ComponentB componentB;
}

class ComponentB implements AutoComposable<...> {
	@Autowired
    private ComponentC componentC;
}

class ComponentC implements AutoComposable<...> {
    // 无可变依赖
}

当扫描 ComponentA 时:

  1. Spring 返回其依赖:ComponentB
  2. 递归扫描 ComponentB
    • 返回依赖:ComponentC
  3. 递归扫描 ComponentC
    • 无依赖,结束

最终结果:也就形成了一个DAG

ComponentA.class -> [ComponentB.class, ComponentC.class]

代码流程图示意

+----------------------------------+
| getBeans(beanName)               |
| - 开始递归扫描                   |
+----------------------------------+
              ↓
+----------------------------------+
| scan(beanName, scannedBeanNames, beans) |
| - 已扫描?                        |
| - 获取 Spring 依赖                |
| - 是否是 AutoComposable?         |
| - 不是 → 再次 scan               |
| - 是 → 加入 beans                 |
+----------------------------------+
              ↓
+----------------------------------+
| Spring.getBeanFactory()           |
| .getDependenciesForBean(beanName)|
+----------------------------------+
              ↓
+----------------------------------+
| 递归处理每一个依赖 BeanName      |
+----------------------------------+

③ 生成可视化流程图(PNG)

使用技术栈:

  • JGraphT:Java 图库,构建 DAG
  • mxGraph:用于渲染图形
  • JGraphXAdapter:JGraphT 与 mxGraph 的适配器

示例代码片段:

DefaultDirectedGraph<String, DefaultEdge> directedGraph = new DefaultDirectedGraph<>(DefaultEdge.class);

Map<Class<? extends AutoComposable>, String> vertexDescMap = cacheMap.keySet().stream()
        .collect(Collectors.toMap(Function.identity(), item -> getAutoComposableDesc(item, "%s\n%s")));

cacheMap.forEach((key, value) -> value.forEach(item ->
        directedGraph.addEdge(vertexDescMap.get(AopUtils.getTargetClass(item)), vertexDescMap.get(key))));

然后通过 mxHierarchicalLayout 进行层次化布局,最后生成 PNG 图片。


三. 总结

整个 dependency 模块的流程图如下:

+--------------------------------+
| ApplicationReadyEvent Listener |
| - 监听 Spring Boot 启动完成   |
+--------------------------------+
              ↓
+----------------------------------+
| 获取所有 AutoComposable Bean     |
| - 过滤 ScopedTarget 代理类      |
+----------------------------------+
              ↓
+----------------------------------+
| 遍历每个组件 |
| - 扫描其 Spring 依赖 BeanName |
| - 递归查找所有依赖组件          |
+----------------------------------+
              ↓
+----------------------------------+
| 将依赖关系缓存到 cacheMap      |
| - key: Component.class          |
| - value: 依赖的 Components Set |
+----------------------------------+
              ↓
+----------------------------------+
| 可选:生成 DAG 文本描述         |
| 可选:生成 DAG 可视化 PNG 图片 |
+----------------------------------+

优点:

避免手动维护依赖关系

  • 不需要写 .yaml.properties 文件维护依赖
  • 全部由 Spring 自动注入 + 框架自动扫描

可打印 DAG 图

  • 在单元测试中调用 generateDependenciesImg()generateDependenciesString()
  • 可快速验证组件依赖是否正确

✅ 最后一句话总结:

dependency 模块是自动编排引擎中构建组件依赖关系(DAG)的核心机制,它利用 Spring 容器自动注入特性,配合递归扫描、JGraphT、mxGraph 等技术,实现了组件依赖的自动解析、缓存、可视化以及循环依赖防护,为后续的自动编排执行提供了坚实的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2385530.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MC】红石比较器

在《我的世界》&#xff08;Minecraft&#xff09;中&#xff0c;红石比较器&#xff08;Redstone Comparator&#xff09; 是一种高级红石元件&#xff0c;主要用于 检测、比较或处理信号强度&#xff0c;同时还能与容器、特定方块互动。 红石比较器有两种模式&#xff1a; 比…

红黑树简单模拟实现

定义成员变量旋转insert以234树的角度来待插入操作具体代码 完整代码 我们前面实现了 二叉搜索树和 AVL树。 其中AVL树是二叉搜索树的改进&#xff0c;但是有些人觉得二叉树搜索的插入调整太频繁了&#xff0c;或者说平衡条件过于苛刻。 于是人们放松了左右子树高度差的限制&…

豪越科技:消防应急装备智能仓储管理新变革

在消防救援工作中&#xff0c;消防装备无疑是消防员们与火灾等灾害顽强对抗的关键“武器”。然而&#xff0c;传统的消防装备管理模式长期以来饱受诸多痛点的困扰&#xff0c;严重影响着消防工作的高效开展和救援效果。 在过去&#xff0c;装备丢失的情况时有发生。由于缺乏有效…

如何设计Agent的记忆系统

最近看了一张画Agent记忆分类的图 我觉得分类分的还可以&#xff0c;但是太浅了&#xff0c;于是就着它的逻辑&#xff0c;仔细得写了一下在不同的记忆层&#xff0c;该如何设计和选型 先从流程&#xff0c;作用&#xff0c;实力和持续时间的这4个维度来解释一下这几种记忆&am…

毕业论文格式(Word)

目录 Word目录怎么自动生成&#xff1f;快速生成试试这3个方法&#xff01; - 知乎https://zhuanlan.zhihu.com/p/692056836目录生成需要先设置标题样式&#xff0c;这个不仅是目录生成需要&#xff0c;和后续的图表也有关系。 最好不要自己创建新的样式&#xff0c;而是在现有…

学习STC51单片机14(芯片为STC89C52RC)

接下来我们进入学会了HC—SR04 还有舵机那么现在我们将他们融合在一起&#xff0c;用超声波来引导舵机的转动 我们这个最后的成果是做一个智能垃圾桶 成品是这样的&#xff0c;是不是可有意思了 成品视频 现在我们将舵机的代码和超声波测距模块的代码整合到一起&#xff0c;实…

基于CodeBuddy实现本地网速的实时浏览小工具

本文所使用的 CodeBuddy 免费下载链接&#xff1a;腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 前言 在数字化浪潮席卷全球的今天&#xff0c;网络已成为人们生活和工作中不可或缺的基础设施。无论是在线办公、学习、娱乐&#xff0c;还是进行大数据传输和云计算&…

stable diffusion论文解读

High-Resolution Image Synthesis with Latent Diffusion Models 论文背景 LDM是Stable Diffusion模型的奠基性论文 于2022年6月在CVPR上发表 传统生成模型具有局限性&#xff1a; 扩散模型&#xff08;DM&#xff09;通过逐步去噪生成图像&#xff0c;质量优于GAN&#x…

计算机网络(3)——传输层

1.概述 1.1 传输层的服务和协议 (1)传输层为允许在不同主机(Host)上的进程提供了一种逻辑通信机制 (2)端系统(如手机、电脑)运行传输层协议 发送方&#xff1a;将来自应用层的消息进行封装并向下提交给 网络层接收方&#xff1a;将接收到的Segment进行组装并向上提交给应用层 …

LangChain构建RAG的对话应用

目录 Langchain是什么&#xff1f; LangSmith是什么&#xff1f; ​编辑 使用Python构建并使用AI大模型 数据解析器 提示模版 部署 记忆功能 Chat History -- 记忆 代码执行流程&#xff1a; 流式输出 构建向量数据库和检索器 检索器 代码执行流程 LLM使用检索器…

目标检测DN-DETR(2022)详细解读

文章目录 gt labels 和gt boxes加噪query的构造attention maskIS&#xff08;InStability&#xff09;指标 在DAB-Detr的基础上&#xff0c;进一步分析了Detr收敛速度慢的原因&#xff1a;二分图匹配的不稳定性&#xff08;也就是说它的目标在频繁地切换&#xff0c;特别是在训…

嵌入式培训之系统编程(四)进程

一、进程的基本概念 &#xff08;一&#xff09;定义 进程是一个程序执行的过程&#xff08;也可以说是正在运行的程序&#xff09;&#xff0c;会去分配内存资 源&#xff0c;cpu的调度&#xff0c;它是并发的 &#xff08;二&#xff09;PCB块 1、PCB是一个结构体&#x…

天文数据处理:基于CUDA的射电望远镜图像实时去噪算法(开源FAST望远镜数据处理代码解析)

一、射电天文数据处理的挑战与CUDA加速的必要性 作为全球最大的单口径射电望远镜&#xff0c;中国天眼&#xff08;FAST&#xff09;每秒产生38GB原始观测数据&#xff0c;经预处理后生成数千万张图像。这些数据中蕴含的脉冲星、中性氢等天体信号常被高斯白噪声、射频干扰&…

VS编码访问Mysql数据库

安装 MySQL Connector/C 的开发包 libmysqlcppconn-dev是 MySQL Connector/C 的开发包&#xff0c;它的主要用途是让 C 开发者能够方便地在应用程序中与 MySQL 数据库进行交互。它提供了以下功能&#xff1a; 数据库连接&#xff1a;通过标准的 C 接口连接到 MySQL 数据库。S…

一周学会Pandas2 Python数据处理与分析-Pandas2数据合并与对比-pd.merge():数据库风格合并

锋哥原创的Pandas2 Python数据处理与分析 视频教程&#xff1a; 2025版 Pandas2 Python数据处理与分析 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili pd.merge()&#xff1a;数据库风格合并 **核心功能**&#xff1a;基于列值&#xff08;类似 SQL JOIN&#xff09;合…

CodeBuddy 实现图片转素描手绘工具

本文所使用的 CodeBuddy 免费下载链接&#xff1a;腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 前言 最近在社交媒体上&#xff0c;各种素描风格的图片火得一塌糊涂&#xff0c;身边不少朋友都在分享自己的 “素描照”&#xff0c;看着那些黑白线条勾勒出的独特韵味&a…

3.8.2 利用RDD计算总分与平均分

在本次实战中&#xff0c;我们利用Spark的RDD完成了成绩文件的总分与平均分计算任务。首先&#xff0c;准备了包含学生成绩的文件并上传至HDFS。接着&#xff0c;通过交互式方式逐步实现了成绩的读取、解析、总分计算与平均分计算&#xff0c;并最终输出结果。此外&#xff0c;…

29-FreeRTOS事件标志组

一、概述 事件是一种实现任务间通信的机制&#xff0c;主要用于实现多任务间的同步&#xff0c;但事件通信只能是事件类型的通信&#xff0c;无数据传输。与信号量不同的是&#xff0c;它可以实现一对多&#xff0c;多对多的同步。 即一个任务可以等待多个事件的发生&#xff1…

「EMD/EEMD/VMD 信号分解方法 ——ECG信号处理-第十四课」2025年5月23日

一、引言 上一节&#xff0c;我们介绍了希尔伯特黄变换&#xff08;HHT&#xff09;及其经验模态分解&#xff08;EMD&#xff09;的相关内容&#xff0c;这一节&#xff0c;我们继续拓展EMD分解技术&#xff0c;补充介绍集合经验模态分解&#xff08;Ensemble Empirical Mode …

二叉树层序遍历6

INT_MIN的用法&#xff1a; INT_MIN是C/C 中的一个宏常量 &#xff0c;在 <limits.h> &#xff08;C 中也可使用 <climits> &#xff09;头文件中定义&#xff0c;代表 int 类型能表示的最小整数值 。其用法主要体现在以下方面&#xff1a; 1.初始化变量 …