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.factories
和 META-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.factories | META-INF/spring/xxx.imports |
---|---|---|
引入版本 | Spring Boot 1.x | Spring 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 | 缓存组件间依赖关系,支持生成字符串和图片 |
AutoComposableDependenciesCacheInitializeListener | Spring 启动后初始化依赖缓存 |
DependentAutoComposableBeanUtil(内部工具类) | 扫描组件依赖的 Bean,并递归处理 |
② 核心流程详解
AutoComposableDependenciesCache
🧠 什么是 DAG?
- DAG = Directed Acyclic Graph(有向无环图)
- 在本框架中:
- 每个组件是一个节点
- 依赖关系是有向边
- 循环依赖会被检测并抛异常
我们先说下 AutoComposableDependenciesCache
类,其中关键数据结构:
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;
📌 存储结构说明:
Key | Value |
---|---|
组件类 Class 对象 (实现了 AutoComposable 接口) | 该组件直接依赖的其他组件(实现了 AutoComposable 接口)集合 |
例如:
FlightSearchComponent.class -> [FlightInfoComponent.class, FlightPricingComponent.class]
AutoComposableDependenciesCacheInitializeListener
AutoComposableDependenciesCacheInitializeListener
这个类是一个 Spring
的 ApplicationListener<ApplicationReadyEvent>
,它的主要作用:
在 Spring Boot 启动完成之后,自动执行一次 DAG 构建操作
✅ 核心步骤:重写 onApplicationEvent
函数
public class AutoComposableDependenciesCacheInitializeListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {}
}
-
获取所有AutoComposable实现类
Map<String, AutoComposable> autoComposableBeanMap = event.getApplicationContext().getBeansOfType(AutoComposable.class);
-
过滤掉代理 Bean(ScopedTarget)
autoComposableBeanMap = autoComposableBeanMap.entrySet().stream() .filter(entry -> !ScopedProxyUtils.isScopedTarget(entry.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
-
初始化 DAG 缓存 map
AutoComposableDependenciesCache.INSTANCE.initCacheMap(autoComposableBeanMap.size());
-
遍历每个组件,构建其依赖关系
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;
}
参数说明:
applicationContext
:Spring
容器上下文,用来获取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); // 递归扫描
}
}
🔍 流程说明:
- 如果当前
beanName
已经扫描过 → 停止递归(防循环) - 否则标记已扫描,继续处理其依赖项
- 遍历
Spring
返回的依赖BeanName
:- 若是另一个
AutoComposable
实现类 → 添加到结果集 - 若不是 → 递归扫描该
Bean
的依赖
- 若是另一个
- 最终返回完整的依赖集合
获取 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);
}
📌 功能说明:
- 处理
ScopedTarget
代理Bean
- 某些组件可能被
AOP
代理,这里尝试获取真实Class
对应的BeanName
- 某些组件可能被
- 调用 Spring 的
getDependenciesForBean()
方法- 这是 Spring 提供的标准
API
,可获取指定BeanName
的依赖列表
- 这是 Spring 提供的标准
- 返回当前
Bean
直接依赖的BeanNames
数组
示例说明
假设你的 Spring 容器中有以下三个组件:
class ComponentA implements AutoComposable<...> {
@Autowired
private ComponentB componentB;
}
class ComponentB implements AutoComposable<...> {
@Autowired
private ComponentC componentC;
}
class ComponentC implements AutoComposable<...> {
// 无可变依赖
}
当扫描 ComponentA
时:
Spring
返回其依赖:ComponentB
- 递归扫描
ComponentB
:- 返回依赖:
ComponentC
- 返回依赖:
- 递归扫描
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
等技术,实现了组件依赖的自动解析、缓存、可视化以及循环依赖防护,为后续的自动编排执行提供了坚实的基础。