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

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

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.MyAutoConfigurationorg.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> {@Overridepublic 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<...> {@Autowiredprivate ComponentB componentB;
}class ComponentB implements AutoComposable<...> {@Autowiredprivate 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.lryc.cn/news/2385530.html

相关文章:

  • 【MC】红石比较器
  • 危化品经营单位安全生产管理人员考试主要内容
  • get_the_category() 和 get_the_terms() 的区别
  • 红黑树简单模拟实现
  • 豪越科技:消防应急装备智能仓储管理新变革
  • 如何设计Agent的记忆系统
  • 毕业论文格式(Word)
  • 学习STC51单片机14(芯片为STC89C52RC)
  • 基于CodeBuddy实现本地网速的实时浏览小工具
  • stable diffusion论文解读
  • 计算机网络(3)——传输层
  • LangChain构建RAG的对话应用
  • 目标检测DN-DETR(2022)详细解读
  • 嵌入式培训之系统编程(四)进程
  • 天文数据处理:基于CUDA的射电望远镜图像实时去噪算法(开源FAST望远镜数据处理代码解析)
  • VS编码访问Mysql数据库
  • 一周学会Pandas2 Python数据处理与分析-Pandas2数据合并与对比-pd.merge():数据库风格合并
  • leetcode 862. 和至少为 K 的最短子数组
  • CodeBuddy 实现图片转素描手绘工具
  • 3.8.2 利用RDD计算总分与平均分
  • 29-FreeRTOS事件标志组
  • 天地图实景三维数据分享(江苏)
  • Jenkins的Pipline中有哪些区块,以及其它知识点整理
  • 「EMD/EEMD/VMD 信号分解方法 ——ECG信号处理-第十四课」2025年5月23日
  • 二叉树层序遍历6
  • 【论文精读】2023 AAAI--FastRealVSR现实世界视频超分辨率(RealWorld VSR)
  • IPython 常用魔法命令
  • 数据同步自动化——如何用Python打造高效工具?
  • 开源与闭源之争:AI时代的创新博弈与未来抉择
  • flutter dart class语法说明、示例