tx_di
基于 proc_macro + linkme 的编译期依赖注入框架。
核心特性:Singleton / Prototype 作用域、#[tx_cst(expr)] 自定义值注入、自动依赖拓扑排序。
github: https://github.com/txtxtxtx/tx_di.git gitee: https://gitee.com/tian_xiong/tx_di.git
快速上手
use HashMap;
use ;
use ;
// 使用插件只需要引入即可,会自动注入和配置
use tx_di_log;
// ── 单例组件(默认)──────────────────────────────────────
// ── 带自定义注入值的单例 ─────────────────────────────────
// ── 原型组件(每次注入独立实例)──────────────────────────
// ── 依赖其他组件的服务 ───────────────────────────────────
// ── 聚合组件:混用单例 + 原型 + 自定义注入 ───────────────
// ── 声明模块,自动生成 build_app_module() ────────────────
// 不指定组件列表时,自动扫描所有 #[tx_comp] 标记的组件
app!
核心概念
Scope(作用域)
| 作用域 | 声明方式 | 行为 |
|---|---|---|
| Singleton(默认) | #[tx_comp] |
全局共享,首次注入时构建,缓存 Arc<T> |
| Prototype | #[tx_comp(scope = Prototype)] |
每次注入调用工厂,构造新实例 |
字段声明方式
| 写法 | 语义 |
|---|---|
field: Arc<T> |
从 DI 容器注入,框架根据 T 的 scope 自动处理 |
#[tx_cst(expr)] + 任意类型 |
不走 DI,直接用表达式赋值,不计入依赖图 |
字段注入示例
关键原则:scope 标记在被注入者上,消费者不需要知道依赖是单例还是原型。
组件初始化(CompInit)
组件可以实现 CompInit trait 来执行自定义的初始化逻辑(同步或异步):
use ;
// 启用 init 支持
重要提示:
async_init返回的BoxFuture<'static, ()>要求生命周期为'static- 不能在 async 块中直接借用
ctx,需要先提取所需数据再移动进 async 块 init()和async_init()会在所有依赖构建完成后按init_sort()排序依次调用
配置文件加载
tx_di 支持从 TOML 配置文件加载组件配置。使用 #[tx_comp(conf)] 标记的组件会自动从配置文件中读取对应的配置段。
配置文件示例
# local/di-config.toml
[]
= "my-app"
= 8080
[]
= "localhost"
= 5432
= "mydb"
配置组件定义
use Deserialize;
/// 应用配置(从配置文件加载)
// 自动从配置文件的 [app_config] 段加载
使用配置文件
// 方式 1:从配置文件加载
let mut ctx = new;
// 方式 2:自动扫描(不使用配置文件)
let mut ctx = ;
ctx.run.await;
// 注入配置组件
let config = ctx.;
println!;
访问全局配置对象
框架会自动创建 AppAllConfig 单例,可以直接访问原始 TOML 数据:
let global_config = ctx.;
// 获取配置值
if let Some = global_config.
// 带默认值的获取
let port = global_config.get_or_default;
// 访问嵌套配置
let db_host = global_config.;
#[tx_cst(expr)]
用于标记字段使用自定义表达式初始化,而不是从 DI 容器注入。
#[tx_cst(expr)] 字段不计入 DEP_IDS,不参与依赖图拓扑排序。
架构三层
用户代码
#[tx_comp(scope = Prototype)] struct Logger { #[tx_cst(...)] prefix: String }
#[tx_comp] struct AppServer { logger: Arc<Logger>, db: Arc<DbPool> }
app! { AppModule } // 自动扫描所有组件
│
│ proc_macro 展开
▼
tx-di-macros
1. 解析 scope 参数 → Scope::Singleton / Scope::Prototype
2. 解析字段:Arc<T>(DI 注入) / #[tx_cst(expr)](自定义值)
3. 生成 ComponentDescriptor impl(含 DEP_IDS + SCOPE + build())
4. 生成 linkme distributed_slice 注册条目
5. app!{} 生成 build_app_module(),自动拓扑排序并注册
│
│ 链接器合并 link section
▼
tx-di-core
- BuildContext:TypeId → CompRef 映射,支持并发访问(DashMap)
- CompRef:内部类型擦除(Cached(Arc<dyn Any>) / Factory(fn))
- COMPONENT_REGISTRY:全局组件元数据切片(linkme 收集)
- topo_sort:自动拓扑排序,检测循环依赖
BuildContext API
let mut ctx = build_app_module;
// 注入组件(根据组件自身的 scope 自动处理)
let db: = ctx.; // 单例:返回缓存的 Arc
let logger: = ctx.; // 原型:构造新实例
// 取走所有权(仅用于单例,会移除缓存)
let owned: AppServer = ctx.;
// 调试:打印所有注册的组件及其依赖
debug_registry;
// 获取组件数量
println!;
println!;
关键设计决策
1. Scope 标记在被注入者上
组件自己声明是 Singleton 还是 Prototype,消费者只需要写 Arc<T>,框架自动处理。
2. 自动拓扑排序
app!{} 不指定组件列表时,会自动从 COMPONENT_REGISTRY 收集所有组件,进行拓扑排序后按依赖顺序注册。
3. 原型不预构建
Scope::Prototype 组件在初始化时只注册工厂函数,不立即构建实例,保证每次 inject() 都是全新实例。
4. #[tx_cst(expr)] 字段不进依赖图
宏解析字段时,有 #[tx_cst] 的字段不加入 DEP_IDS,不影响拓扑排序,也不要求对应类型在 ctx 中存在。
5. 并发安全
使用 DashMap 存储组件实例,支持多线程环境下的并发注入。
约束
| 约束 | 原因 |
|---|---|
组件需 T: Send + Sync + 'static |
存入 Arc<dyn Any + Send + Sync>,支持并发 |
组件需 Clone(推荐) |
便于在多个地方共享 |
无字段组件自动构建为 Self {} |
需要 struct 可默认构造 |
take() 只能用于单例 |
原型组件没有缓存,无法 take |
| 避免循环依赖 | 框架会在运行时检测并 panic |
async_init 返回 'static Future |
异步任务不能持有 ctx 引用,需先提取数据 |
测试
测试覆盖范围
项目包含 30+ 个测试用例,全面覆盖框架的核心功能:
单例测试 (3个)
test_singleton_shared- 验证单例在不同组件间共享test_singleton_multiple_injects_same_instance- 多次注入返回相同实例test_singleton_arc_clone_shares_data- Arc clone 只增加引用计数
原型测试 (3个)
test_prototype_independent- 验证原型实例相互独立test_prototype_each_inject_creates_new_instance- 每次注入创建新实例test_prototype_with_custom_values- 验证原型的自定义值注入
自定义值注入测试 (3个)
test_inject_custom_values- 验证 HashMap 和 String 注入test_app_config_inject- 验证函数调用和字面量注入test_custom_value_expression_evaluated_once- 验证表达式只求值一次
依赖关系测试 (2个)
test_dependency_injection_chain- 验证依赖注入链正确性test_user_service_functionality- 验证服务功能正常
注册表测试 (2个)
test_registry- 打印所有组件及依赖名称test_scope_on_component- 验证 scope 标记在组件自身
BuildContext API 测试 (3个)
test_build_context_len_and_empty- 验证初始状态test_build_context_after_initialization- 验证初始化后状态test_take_removes_from_context- 验证 take 移除组件
边界情况测试 (4个)
test_singleton_thread_safety- 多线程环境下的单例安全性test_prototype_state_isolation- 原型实例状态隔离test_component_with_no_dependencies- 无依赖组件注入test_component_with_multiple_dependencies- 多依赖组件注入
调试功能测试 (1个)
test_debug_registry_output- 验证调试输出不 panic
配置文件加载测试 (9个)
test_load_from_config_file- 测试从配置文件加载组件test_config_file_values_loaded_to_app_config- 验证配置文件中的值被正确加载到 AppConfigtest_global_config_access_raw_toml- 测试全局配置对象 AppAllConfig 可以访问原始 TOML 数据test_missing_config_file_uses_defaults- 测试配置文件不存在时使用默认值test_config_get_or_default- 测试 get_or_default 方法test_complex_config_nested_access- 测试复杂配置文件的多层级访问test_config_value_type_conversion- 测试配置值的类型转换test_app_all_config_is_singleton- 测试 AppAllConfig 在上下文中是单例test_auto_scan_mode- 测试自动扫描模式
运行示例
# 运行所有测试
# 运行特定测试
# 运行测试并显示输出
# 运行测试并显示时间