tx-di-core 0.1.2

DI framwork,依赖自动注入框架,运行时使用拓扑排序,排序依赖关系
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# 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_di 支持从 TOML 配置文件加载组件配置。使用 `#[tx_comp(conf)]` 标记的组件会自动从配置文件中读取对应的配置段。

#### 配置文件示例


```toml
# local/di-config.toml

[app_config]
app_name = "my-app"
port = 8080

[database]
host = "localhost"
port = 5432
name = "mydb"
```

#### 配置组件定义


```rust
use serde::Deserialize;

/// 应用配置(从配置文件加载)
#[derive(Clone, Debug, Deserialize, Default)]

#[tx_comp(conf)]  // 自动从配置文件的 [app_config] 段加载

pub struct AppConfig {
    pub app_name: String,
    pub port: u16,
}
```

#### 使用配置文件


```rust
// 方式 1:从配置文件加载
let mut ctx = BuildContext::new(Some("local/di-config.toml"));

// 方式 2:自动扫描(不使用配置文件)
let mut ctx = BuildContext::new::<PathBuf>(None);

ctx.run().await;

// 注入配置组件
let config = ctx.inject::<AppConfig>();
println!("App: {}, Port: {}", config.app_name, config.port);
```

#### 访问全局配置对象


框架会自动创建 `AppAllConfig` 单例,可以直接访问原始 TOML 数据:

```rust
let global_config = ctx.inject::<AppAllConfig>();

// 获取配置值
if let Some(app_name) = global_config.get::<String>("app_config.app_name") {
    println!("App name: {}", app_name);
}

// 带默认值的获取
let port = global_config.get_or_default("app_config.port", 8080);

// 访问嵌套配置
let db_host = global_config.get::<String>("database.host");
```

### `#[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
```

### 测试覆盖范围


项目包含 **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` - 验证配置文件中的值被正确加载到 AppConfig
- `test_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` - 测试自动扫描模式

### 运行示例


```bash
# 运行所有测试

cargo test

# 运行特定测试

cargo test test_singleton_shared

# 运行测试并显示输出

cargo test -- --nocapture

# 运行测试并显示时间

cargo test -- --show-output
```