tx-di-core 0.3.0

DI framwork,依赖自动注入框架,运行时使用拓扑排序,排序依赖关系
Documentation

tx_di

基于 proc_macro + linkme 的 Rust 依赖注入框架。编译期收集元数据,运行期自动拓扑排序并注入,零反射、零运行时扫描。


快速上手

依赖

[dependencies]
tx-di-core = "0.2"

基本用法

use std::sync::Arc;
use tx_di_core::{tx_comp, tx_cst, BuildContext};

// 无依赖单例
#[tx_comp]
pub struct DbPool;

// 带自定义值
#[tx_comp]
pub struct AppConfig {
    #[tx_cst("my-app".to_string())]
    pub name: String,
}

// 依赖其他组件
#[tx_comp]
pub struct UserService {
    pub db: Arc<DbPool>,
    pub config: Arc<AppConfig>,
}

#[tokio::main]
async fn main() {
    let ctx = BuildContext::new::<std::path::PathBuf>(None);
    let svc = ctx.inject::<UserService>();
}

属性宏 #[tx_comp]

写法 说明
#[tx_comp] 默认 Singleton
#[tx_comp(scope = Prototype)] 每次注入创建新实例
#[tx_comp(scope)] 同 Prototype
#[tx_comp(init)] 自行实现 CompInit trait
#[tx_comp(conf)] 从 TOML 配置自动反序列化
#[tx_comp(conf = "key")] 指定 TOML 配置键
#[tx_comp(as_trait = dyn Trait)] 注册为 trait 实现,支持 trait object 注入

可组合使用:#[tx_comp(scope = Prototype, as_trait = dyn MyTrait)]


字段注入

写法 语义
field: Arc<T> 从 DI 容器注入,按 T 的 scope 处理
#[tx_cst(expr)] 直接用表达式赋值,不进依赖图
#[tx_cst(skip)] 跳过,使用 Default::default()
Option<T> 字段 自动设为 None
#[tx_comp]
pub struct MyService {
    pub db: Arc<DbPool>,                            // DI 注入
    #[tx_cst("0.0.0.0:8080".to_string())]
    pub addr: String,                                // 自定义值
    #[tx_cst(RwLock::new(HashMap::new()))]
    pub cache: RwLock<HashMap<u64, String>>,         // 自定义值
    #[tx_cst(skip)]
    pub temp: Vec<u8>,                               // 跳过
}

Trait Object 注入

通过 as_trait 将具体类型注册为 trait 实现,注入时自动解析。

要求:trait 需要 Any + Send + Sync 作为 supertrait。

// 定义 trait
pub trait UserRepository: Any + Send + Sync {
    async fn find_by_id(&self, id: u64) -> AppResult<Option<User>>;
}

// 注册实现
#[tx_comp(as_trait = dyn UserRepository)]
pub struct SqliteUserRepository {
    #[tx_cst(RwLock::new(HashMap::new()))]
    users: RwLock<HashMap<u64, User>>,
}

// 注入 trait object
#[tx_comp]
pub struct UserService {
    pub user_repo: Arc<dyn UserRepository>,   // 自动解析到 SqliteUserRepository
}

一个 trait 可以有多个实现,通过 inject_all_traits_from_store 获取全部:

let repos: Vec<Arc<dyn UserRepository>> = inject_all_traits_from_store(store);

作用域 Scope

作用域 行为
Singleton(默认) 全局唯一,首次注入时构建并缓存
Prototype 每次 inject() 创建新实例

scope 标记在被注入者上,消费者只需写 Arc<T>


配置组件

# configs/app.toml
[my_config]
app_name = "production"
port = 9090
use serde::Deserialize;

#[derive(Deserialize, Default)]
#[tx_comp(conf)]                // 从 [my_config] 加载
pub struct MyConfig {
    #[serde(default)]
    pub app_name: String,
    pub port: u16,
}

// 自定义键名
#[tx_comp(conf = "server")]
pub struct ServerConfig { ... }

加载配置文件:

let ctx = BuildContext::new(Some("configs/app.toml"));
let ctx = BuildContext::new::<std::path::PathBuf>(None);  // 不用配置

自定义初始化 CompInit

use tx_di_core::{tx_comp, CompInit, App, RIE, CancellationToken};
use tx_di_core::async_method;

#[tx_comp(init)]
pub struct AppServer {
    pub addr: String,
}

impl CompInit for AppServer {
    // 组件构建后立即调用(BuildContext 阶段)
    fn inner_init(&mut self, ctx: &InnerContext) -> RIE<()> {
        println!("inner_init");
        Ok(())
    }

    // 同步初始化
    fn init(ctx: Arc<App>, token: CancellationToken) -> RIE<()> {
        println!("init");
        Ok(())
    }

    // 异步初始化(用 async_method! 简化)
    async_method!(
        fn async_init_impl(ctx: Arc<App>, token: CancellationToken) -> RIE<()> {
            println!("async_init");
            Ok(())
        }
    );

    // 异步运行(长期任务)
    async_method!(
        fn async_run_impl(ctx: Arc<App>, token: CancellationToken) -> RIE<()> {
            println!("running");
            Ok(())
        }
    );

    // 初始化顺序(值越小越先执行,默认 10000)
    fn init_sort() -> i32 { 100 }
}

BuildContext & App

// 构建阶段
let ctx = BuildContext::new(Some("configs/app.toml"));
let db: Arc<DbPool> = ctx.inject::<DbPool>();
BuildContext::debug_registry();       // 打印注册表
let app: App = ctx.build()?;          // 固化

// 或快捷方式
ctx.build_and_run().await?;           // build + init + async_init

// App 阶段(支持 Singleton 和 Prototype)
let app = Arc::new(ctx.build()?);
let db = app.inject::<DbPool>();
let logger = app.inject::<RequestLogger>();  // Prototype 每次新建

// 异步运行(后台任务管理)
let app = ctx.ins_run().await?;       // 返回 Arc<App>,后台初始化
app.waiting_exit().await;             // 等待 Ctrl+C 信号

约束

约束 原因
组件需 T: Send + Sync + 'static 存入 Arc<dyn Any + Send + Sync>
配置组件需 Deserialize + Default serde 反序列化
trait 注入需 Trait: Any + Send + Sync TypeId::of::<dyn Trait>() 要求
避免循环依赖 拓扑排序检测,存在则 panic
插件 crate 必须 use 导入 linkme 依赖链接器

许可证

MIT