# tx_di
基于 `proc_macro` + `linkme` 的编译期依赖注入框架。
**核心特性**:Singleton / Prototype 作用域、`#[tx_cst(expr)]` 自定义值注入、自动依赖拓扑排序。
## 快速上手
```rust
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tx_di_core::{app, tx_comp, tx_cst};
// ── 单例组件(默认)──────────────────────────────────────
#[derive(Clone, Debug)]
#[tx_comp]
pub struct DbPool {
// 无字段组件自动构建
}
// ── 带自定义注入值的单例 ─────────────────────────────────
#[derive(Clone, Debug)]
#[tx_comp]
pub struct AppConfig {
#[tx_cst("my-app".to_string())]
pub app_name: String,
#[tx_cst(default_port())]
pub port: u16,
}
fn default_port() -> u16 {
8080
}
// ── 原型组件(每次注入独立实例)──────────────────────────
#[derive(Clone, Debug)]
#[tx_comp(scope = Prototype)]
pub struct RequestLogger {
#[tx_cst("[REQUEST]".to_string())]
pub prefix: String,
#[tx_cst(Arc::new(Mutex::new(0u64)))]
count: Arc<Mutex<u64>>,
}
impl RequestLogger {
pub fn log(&self, msg: &str) {
let mut c = self.count.lock().unwrap();
*c += 1;
println!("{} [#{}] {}", self.prefix, *c, msg);
}
}
// ── 依赖其他组件的服务 ───────────────────────────────────
#[derive(Clone, Debug)]
#[tx_comp]
pub struct UserService {
pub db: Arc<DbPool>,
pub config: Arc<AppConfig>,
}
// ── 聚合组件:混用单例 + 原型 + 自定义注入 ───────────────
#[derive(Debug)]
#[tx_comp]
pub struct AppServer {
pub user_svc: Arc<UserService>, // 单例注入
pub logger: Arc<RequestLogger>, // 原型注入,独占实例
#[tx_cst(HashMap::new())]
pub headers: HashMap<String, String>, // 自定义值,不走 DI
#[tx_cst("0.0.0.0:8080".to_string())]
pub bind_addr: String,
}
// ── 声明模块,自动生成 build_app_module() ────────────────
// 不指定组件列表时,自动扫描所有 #[tx_comp] 标记的组件
app! { AppModule }
fn main() {
let mut ctx = build_app_module();
let server = ctx.take::<AppServer>();
println!("Server ready at {}", server.bind_addr);
}
```
## 核心概念
### Scope(作用域)
| **Singleton**(默认) | `#[tx_comp]` | 全局共享,首次注入时构建,缓存 `Arc<T>` |
| **Prototype** | `#[tx_comp(scope = Prototype)]` | 每次注入调用工厂,构造新实例 |
### 字段声明方式
| `field: Arc<T>` | 从 DI 容器注入,框架根据 T 的 scope 自动处理 |
| `#[tx_cst(expr)]` + 任意类型 | 不走 DI,直接用表达式赋值,不计入依赖图 |
### 字段注入示例
```rust
#[tx_comp]
pub struct MyComponent {
// 自动注入:框架根据 DbPool 的 scope 决定是共享还是新建
pub db: Arc<DbPool>,
// 自定义值:直接调用表达式,不参与依赖图
#[tx_cst("custom_value".to_string())]
pub name: String,
// 调用函数
#[tx_cst(load_config())]
pub config: Config,
// 集合类型
#[tx_cst(HashMap::new())]
pub cache: HashMap<String, String>,
}
```
**关键原则**:scope 标记在**被注入者**上,消费者不需要知道依赖是单例还是原型。
### 组件初始化(CompInit)
组件可以实现 `CompInit` trait 来执行自定义的初始化逻辑(同步或异步):
```rust
use tx_di_core::{CompInit, BuildContext, BoxFuture};
#[derive(Debug)]
#[tx_comp(init)] // 启用 init 支持
pub struct AppServer {
pub user_svc: Arc<UserService>,
pub logger: Arc<RequestLogger>,
}
impl CompInit for AppServer {
/// 同步初始化:在所有依赖注入完成后调用
fn init(ctx: &mut BuildContext) {
println!("AppServer 同步初始化,当前组件数: {}", ctx.len());
}
/// 异步初始化:用于需要 async/await 的场景
/// 注意:返回的 Future 必须是 'static,不能持有 ctx 引用
fn async_init(ctx: &mut BuildContext) -> BoxFuture<'static, ()> {
let len = ctx.len(); // 先提取数据
Box::pin(async move {
println!("AppServer 异步初始化,组件数: {}", len);
// 可以执行异步操作,如连接数据库、加载远程配置等
})
}
/// 初始化顺序:数值越小越先初始化,默认 10000
fn init_sort() -> i32 {
1000 // 较晚初始化,确保依赖都已就绪
}
}
```
**重要提示**:
- `async_init` 返回的 `BoxFuture<'static, ()>` 要求生命周期为 `'static`
- 不能在 async 块中直接借用 `ctx`,需要先提取所需数据再移动进 async 块
- `init()` 和 `async_init()` 会在所有依赖构建完成后按 `init_sort()` 排序依次调用
### `#[tx_cst(expr)]`
用于标记字段使用自定义表达式初始化,而不是从 DI 容器注入。
```rust
#[component]
pub struct Config {
// 任意 Rust 表达式
#[inject(std::env::var("APP_NAME").unwrap_or("default".to_string()))]
pub name: String,
// 函数调用
#[inject(load_tls_config())]
pub tls: TlsConfig,
// 字面量
#[inject(42u32)]
pub timeout_secs: u32,
// HashMap、Vec 等集合
#[inject(HashMap::new())]
pub cache: HashMap<String, String>,
// 正常 DI 注入(无 #[inject])
pub db: Singleton<DbPool>,
}
```
`#[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
```rust,ignore
let mut ctx = build_app_module();
// 注入组件(根据组件自身的 scope 自动处理)
let db: Arc<DbPool> = ctx.inject::<DbPool>(); // 单例:返回缓存的 Arc
let logger: Arc<RequestLogger> = ctx.inject::<RequestLogger>(); // 原型:构造新实例
// 取走所有权(仅用于单例,会移除缓存)
let owned: AppServer = ctx.take::<AppServer>();
// 调试:打印所有注册的组件及其依赖
BuildContext::debug_registry();
// 获取组件数量
println!("已注册组件数: {}", ctx.len());
println!("是否为空: {}", ctx.is_empty());
```
## 关键设计决策
### 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` 引用,需先提取数据 |
## 测试
```bash
cargo test
```
### 测试覆盖范围
项目包含 **20+ 个测试用例**,全面覆盖框架的核心功能:
#### 单例测试 (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
### 运行示例
```bash
# 运行所有测试
cargo test
# 运行特定测试
cargo test test_singleton_shared
# 运行测试并显示输出
cargo test -- --nocapture
# 运行测试并显示时间
cargo test -- --show-output
```