#di #scope #framwork #github #api #gitee #git #基于 #linkme

tx-di-core

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

7 releases

Uses new Rust 2024

new 0.2.0 May 9, 2026
0.1.6 Apr 26, 2026

#739 in Parser implementations

MIT license

72KB
873 lines

tx_di

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

Crates.io License: MIT


目录


特性一览

特性 说明
零反射 依赖关系在编译期由宏生成,链接器通过 linkme 收集
Singleton / Prototype 两种作用域,scope 标记在被注入者上,消费者无感知
统一 store-based 注入 build(store: &DashMap) 统一签名,App 阶段也支持 Prototype
自动拓扑排序 Kahn 算法,运行时自动解析构建顺序,循环依赖立即报错
自定义值注入 #[tx_cst(expr)] 支持任意 Rust 表达式,不进入依赖图
TOML 配置加载 #[tx_comp(conf)] 自动从配置文件反序列化组件
生命周期回调 CompInit trait 支持 inner_init / 同步 init / 异步 async_init
并发安全 使用 DashMap 存储实例,Arc<T> 共享,线程安全
插件化 日志、Web、SIP、GB28181、CAN 等开箱即用

快速上手

1. 添加依赖

[dependencies]
tx-di-core = "0.1"
linkme = "0.3"
tokio = { version = "1", features = ["full"] }

2. 定义并注入组件

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

// ── 无依赖单例 ────────────────────────────────────────────────────
#[derive(Clone, Debug)]
#[tx_comp]                          // 默认 Singleton
pub struct DbPool;

// ── 带自定义值的单例 ──────────────────────────────────────────────
#[derive(Clone, Debug)]
#[tx_comp]
pub struct AppConfig {
    #[tx_cst("my-app".to_string())]
    pub app_name: String,

    #[tx_cst(8080u16)]
    pub port: u16,
}

// ── 原型组件(每次注入创建新实例)────────────────────────────────
#[derive(Clone, Debug)]
#[tx_comp(scope = Prototype)]
pub struct RequestLogger {
    #[tx_cst("[REQ]".to_string())]
    pub prefix: String,
}

// ── 依赖其他组件的服务 ────────────────────────────────────────────
#[derive(Clone, Debug)]
#[tx_comp]
pub struct UserService {
    pub db: Arc<DbPool>,            // 自动注入 DbPool 单例
    pub config: Arc<AppConfig>,     // 自动注入 AppConfig 单例
}

// ── main ──────────────────────────────────────────────────────────
#[tokio::main]
async fn main() {
    // 创建上下文,自动扫描所有 #[tx_comp] 组件并按拓扑顺序构建
    let ctx = BuildContext::new::<std::path::PathBuf>(None);

    let svc = ctx.inject::<UserService>();
    println!("app_name: {}", svc.config.app_name);
    println!("port:     {}", svc.config.port);
}

核心概念

作用域 Scope

作用域 宏写法 行为
Singleton(默认) #[tx_comp] 全局唯一,首次注入时构建并缓存 Arc<T>
Prototype #[tx_comp(scope = Prototype)]#[tx_comp(scope)] 每次 inject() 调用工厂创建全新实例

关键原则:scope 标记在被注入者上,消费者只需写 Arc<T>,框架自动处理。

// ✅ Prototype 标记在 RequestLogger 自己身上
#[tx_comp(scope = Prototype)]
pub struct RequestLogger { ... }

// 消费者无需关心 scope
#[tx_comp]
pub struct AppServer {
    pub logger: Arc<RequestLogger>,  // 每次创建新的 logger 实例
}

v0.1.7 新特性:build() 后的 App 阶段也支持 Prototype 注入。

let app = ctx.build()?;
// App 阶段同样可以注入 Prototype
let l1 = app.inject::<RequestLogger>();
let l2 = app.inject::<RequestLogger>();
assert_ne!(Arc::as_ptr(&l1), Arc::as_ptr(&l2));  // 不同实例

字段声明方式

写法 语义
field: Arc<T> 从 DI 容器注入,框架根据 T 的 scope 自动处理
field: T(其他类型) Arc 包裹时,宏仍会尝试 ctx.inject::<T>()
#[tx_cst(expr)] 不走 DI,直接用表达式赋值,不计入依赖图
Option<T> 字段 自动设为 None,不参与依赖注入
#[tx_cst(skip)] 跳过注入,使用 Default::default()
#[tx_comp]
pub struct MyService {
    // ① DI 注入
    pub db: Arc<DbPool>,

    // ② 自定义值:任意表达式,不进依赖图
    #[tx_cst("0.0.0.0:8080".to_string())]
    pub bind_addr: String,

    // ③ 调用函数
    #[tx_cst(default_headers())]
    pub headers: HashMap<String, String>,

    // ④ 集合
    #[tx_cst(Vec::new())]
    pub items: Vec<String>,

    // ⑤ 读取环境变量
    #[tx_cst(std::env::var("SECRET").unwrap_or_default())]
    pub secret: String,
}

#[tx_cst(expr)] 字段不会被加入 DEP_IDS,不影响拓扑排序。


配置组件 conf

#[tx_comp(conf)] 标记的组件会自动从 TOML 配置文件中加载配置,无需手动解析。

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

#[derive(Clone, Debug, Deserialize, Default)]
#[tx_comp(conf)]        // 自动从 [app_config] 段加载
pub struct AppConfig {
    #[serde(default = "default_name")]
    pub app_name: String,

    #[serde(default = "default_port")]
    pub port: u16,
}

fn default_name() -> String { "my-app".to_string() }
fn default_port() -> u16 { 8080 }

自定义配置键

#[tx_comp(conf = "server")]   // 从 TOML 的 [server] 段读取
pub struct MyComponent { ... }

加载配置文件

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

自定义初始化 CompInit

在所有依赖构建完成后,可以通过 CompInit trait 执行同步或异步初始化逻辑。

use tx_di_core::{tx_comp, CompInit, App, BoxFuture, RIE};
use std::sync::Arc;

#[derive(Debug)]
#[tx_comp(init)]        // init flag:告知宏"我自己写 CompInit 实现"
pub struct AppServer {
    pub user_svc: Arc<UserService>,
    pub bind_addr: String,
}

impl CompInit for AppServer {
    fn init(ctx: Arc<App>) -> RIE<()> {
        println!("同步初始化");
        Ok(())
    }

    fn async_init(ctx: Arc<App>) -> BoxFuture<'static, RIE<()>> {
        let addr = ctx.inject::<AppServer>().bind_addr.clone();
        Box::pin(async move {
            println!("异步初始化,bind_addr: {}", addr);
            Ok(())
        })
    }

    fn init_sort() -> i32 { 1000 }
}

组件内部初始化(inner_init:在组件构建时(build() 内部)立即调用,早于 init()
注意:inner_init 仅在 BuildContext 构建阶段调用,App 阶段跳过。


BuildContext API

BuildContext 是构建阶段的主要入口:

use std::path::PathBuf;
use tx_di_core::BuildContext;

// 创建上下文(自动扫描 + 拓扑排序 + 注册所有 #[tx_comp] 组件)
let ctx = BuildContext::new(Some("configs/app.toml"));
let ctx = BuildContext::new::<PathBuf>(None);

// ── 注入 ──────────────────────────────────────────────────────────

// inject: 根据 scope 自动处理(Singleton 缓存 Arc,Prototype 创建新实例)
let db: Arc<DbPool> = ctx.inject::<DbPool>();
let logger: Arc<RequestLogger> = ctx.inject::<RequestLogger>();

// get: 自动区分 Singleton / Prototype
let db: Arc<DbPool> = ctx.get::<DbPool>();

// get_singleton: 无需 ComponentDescriptor 约束
let db: Arc<DbPool> = ctx.get_singleton::<DbPool>();

// try_get_singleton: 安全版本,未找到返回 None
let db: Option<Arc<DbPool>> = ctx.try_get_singleton::<DbPool>();

// take: 移交所有权(仅 Singleton)
let server: AppServer = ctx.take::<AppServer>().expect("未找到 AppServer");

// ── 调试 ──────────────────────────────────────────────────────────

BuildContext::debug_registry();     // 打印所有注册组件及依赖关系
println!("组件数: {}", ctx.len());

// ── 构建固化上下文 ──────────────────────────────────────────────

let app: App = ctx.build()?;
ctx.build_and_run().await?;         // build + init 的快捷方式

App(固化上下文)

AppBuildContext::build() 之后的"固化"状态,通过 Arc<App> 可在多线程中共享。

use std::sync::Arc;
use tx_di_core::{BuildContext, App};

let ctx = BuildContext::new::<std::path::PathBuf>(None);
let app = Arc::new(ctx.build()?);

// Singleton:返回缓存实例
let db = app.inject::<DbPool>();
let db = app.try_inject::<DbPool>();  // Option 安全版本

// Prototype(v0.1.7+):每次创建新实例
let l1 = app.inject::<RequestLogger>();
let l2 = app.try_inject::<RequestLogger>();

println!("组件数: {}", app.len());

v0.1.7 升级:App 阶段现在完整支持 Prototype 注入,try_inject 也能返回 Prototype。


配置文件加载

# configs/app.toml
[app_config]
app_name = "my-app"
port = 8080

[log_config]
level = "info"
dir = "./logs"
console_output = true

[web_config]
host = "0.0.0.0"
port = 8080
enable_cors = true
max_body_size = 10485760    # 10MB

[sip_config]
host = "::"
port = 5060

访问全局配置 AppAllConfig

let global_cfg = ctx.inject::<tx_di_core::AppAllConfig>();

// 读取指定键
let app_name: Option<String> = global_cfg.get("app_config.app_name");
let port: Option<u16>        = global_cfg.get("app_config.port");

// 带默认值读取
let retention = global_cfg.get_or_default("log_config.retention_days", 90u64);

内置插件

插件通过 linkme 在编译期自动注册组件,使用时必须在代码中 use 导入,否则链接器会优化掉。

tx_di_log — 日志插件

基于 tracing + tracing-subscriber,支持按天滚动文件日志、控制台彩色输出、模块级别过滤。

[dependencies]
tx_di_log = "0.1"
use tx_di_core::BuildContext;
use tx_di_log;  // 必须导入

#[tokio::main]
async fn main() {
    let ctx = BuildContext::new(Some("configs/app.toml"));
    tracing::info!("应用启动");
}

配置示例:

[log_config]
level = "info"
dir = "./logs"
console_output = true
time_format = "local"
retention_days = 90
prefix = "my-app"

[log_config.modules]
"my_app::db" = "debug"
"noisy_lib"  = "warn"
字段 类型 默认值 说明
level String "info" 全局日志级别
dir String "./logs" 日志文件目录
console_output bool false 是否输出到控制台
time_format String "utc" 时间格式
retention_days u64 90 日志保留天数
prefix String "tx_di" 日志文件名前缀

tx_di_axum — Web 插件

基于 axum + tokio 的异步 HTTP 服务器,支持 CORS、静态文件、SPA、中间件链路。

[dependencies]
tx_di_axum = "0.1"
use tx_di_core::BuildContext;
use tx_di_axum;
use tx_di_log;

#[tokio::main]
async fn main() {
    let ctx = BuildContext::new(Some("configs/app.toml"));
    ctx.build_and_run().await.expect("启动失败");
    tokio::signal::ctrl_c().await.ok();
}
[web_config]
host = "0.0.0.0"
port = 8080
enable_cors = true
max_body_size = 10485760
timeout_secs = 30
字段 类型 默认值 说明
host String "127.0.0.1" 监听地址(IPv4 / IPv6)
port u16 8080 监听端口
enable_cors bool false 是否启用 CORS
max_body_size usize 10485760 最大请求体
timeout_secs u64 30 请求超时

内置端点:GET /health 健康检查。


tx_di_sip — SIP 协议栈插件

基于 rsipstack 0.5 的 SIP 协议栈包装,提供 SIP 消息路由能力。

[dependencies]
tx_di_sip = "0.1"

核心功能

  • SipPlugin — SIP 传输层(UDP/TCP 监听)
  • SipRouter — 消息处理器注册与分发
  • 支持 REGISTER / INVITE / MESSAGE / NOTIFY / OPTIONS 等标准 SIP 方法

配置

[sip_config]
host = "::"        # 监听地址
port = 5060        # 监听端口
transport = "udp"  # udp / tcp / both

tx_di_gb28181 — GB28181 服务端插件

基于 tx_di_sip 构建的 GB28181-2022 上级平台完整服务端。

核心功能(74 个功能点)

功能 说明
设备注册管理 REGISTER/注销/心跳,支持 SIP 摘要认证
目录查询 Catalog 查询,完整解析通道列表(含经纬度)
实时点播 INVITE s=Play,联动流媒体分配 RTP 端口
历史回放 INVITE s=Playback,含时间范围 SDP
PTZ 云台控制 8 方向 + 变倍 + 聚焦 + 光圈
录像控制/查询 开始/停止录像,录像文件列表查询
报警事件 NOTIFY 报警接收
广播/对讲 语音广播邀请/接收,对讲音频会话
移动位置 GPS 定位上报通知
远程启动 TeleBoot 远程重启
拉框缩放 ZoomIn / ZoomOut
巡航轨迹 巡航轨迹列表/详情查询
PTZ 精准控制 绝对位置云台控制(2022 新增)
存储管理 存储卡格式/状态查询
目标跟踪 目标跟踪控制(2022 新增)
流媒体后端 ZLM / MediaMTX 统一后端接入

tx_di_gb28181_client — GB28181 设备端插件

模拟 GB28181 设备端的插件,实现向平台注册、响应查询、处理点播。

use tx_di_core::BuildContext;
use tx_di_gb28181_client;

#[tokio::main]
async fn main() {
    let ctx = BuildContext::new(Some("configs/gb28181-client.toml"));
    ctx.build_and_run().await?;
}

功能

  • REGISTER 向平台注册(含摘要认证)
  • 心跳保活
  • 响应目录查询、设备信息查询、状态查询
  • 实时点播 INVITE SDP answer
  • 可配置的通道列表和预置位

tx_di_can — CAN/CANFD 插件

嵌入式 CAN 总线通信插件,支持 SocketCAN / PCAN 双适配器,集成 ISO-TP 和 UDS 刷写。

[dependencies]
tx_di_can = "0.1"

核心能力

  • SocketCAN 适配器(Linux PF_CAN)
  • PCAN 适配器(Windows pcanbasic.dll)
  • ISO-TP(ISO 15765-2)多帧传输
  • UDS(ISO 14229)诊断协议
  • 刷写引擎(带安全和完整性校验)

公共库 tx_gb28181

tx_gb28181 是 GB28181-2022 协议公共库,供服务端和客户端插件共享。

模块 内容
device 设备/通道数据类型(DeviceInfo / ChannelInfo / ChannelStatus
cmd_type 协议指令枚举 Gb28181CmdType(15+ 种 CmdType)
event 事件类型 Gb28181Event(27 种) + 全局广播 subscribe / emit
xml MANSCDP XML 构建与解析
sdp SDP 构建与解析(含 IPv4/IPv6 双栈)
sip SIP URI 解析工具
use tx_gb28181::{Gb28181Event, Gb28181CmdType, DeviceInfo, ChannelInfo};

// 订阅全局事件
let mut rx = tx_gb28181::event::subscribe();
tokio::spawn(async move {
    while let Ok(ev) = rx.recv().await {
        match ev {
            Gb28181Event::DeviceRegistered { device_id, .. } => println!("上线: {}", device_id),
            _ => {}
        }
    }
});

架构原理

用户代码(定义组件)
  #[tx_comp]                 struct DbPool {}
  #[tx_comp(scope=Prototype)] struct Logger { #[tx_cst(...)] prefix: String }
  #[tx_comp]                 struct AppServer { db: Arc<DbPool>, log: Arc<Logger> }
         │
         │  proc_macro 展开(编译期)
         ▼
tx-di-macros
  1. 解析 scope 参数   →  Singleton / Prototype
  2. 解析字段注入方式   →  Inject / Custom / Optional / Skip
  3. 生成 ComponentDescriptor implDEP_IDS + SCOPE + build())
  4. 生成 CompInit 默认空实现
  5. 注册 linkme distributed_slice 条目ComponentMeta)
         │
         │  链接器合并 link section运行前)
         ▼
tx-di-core运行时COMPONENT_REGISTRY    全局静态组件元数据切片linkme 收集BuildContext::new()
    ├─ 创建 AppAllConfig 单例加载 TOML)
    └─ auto_register_all()
         ├─  COMPONENT_REGISTRY 收集所有 ComponentMeta
         ├─ topo_sort()(Kahn 算法检测循环依赖
         └─ 按顺序调用 factory(store),Singleton 立即缓存
  BuildContext::build()
    ├─  init_sort 顺序调用 init()(同步)
    ├─ 并行调用所有 async_init()(异步)
    └─ 转移 storeAppApp 也支持 Prototype 注入

v0.1.7 内部变更

变更 说明
build 签名 fn build(ctx: &mut BuildContext)fn build(store: &DashMap<TypeId, CompRef>)
inject 签名 inject(&mut self)inject(&self),不再需要可变引用
CompRef::Factory 接收 &DashMap 而非 &mut BuildContext
App 阶段 现在完整支持 Singleton 和 Prototype 注入
inject_from_store 新增公共辅助函数,供宏生成的 build 方法调用

约束与注意事项

约束 原因
组件需 T: Send + Sync + 'static 存入 Arc<dyn Any + Send + Sync>,支持多线程
配置组件需 Deserialize + Default 配置键不存在时使用 serde 默认值
take() 仅用于 Singleton Prototype 无缓存,无法取出所有权
插件 crate 必须 use 导入 linkme 依赖链接器,未引用的 crate 会被优化掉
避免循环依赖 拓扑排序时检测,存在则 panic 并列出循环节点
async_init 返回 'static Future 不能在 async 块中直接借用 ctx
inner_init 仅在 BuildContext 阶段调用 App 阶段跳过(无 BuildContext 可用)

测试

# 运行所有测试
cargo test

# 只运行 DI 核心测试(28+ 个,含 6 个 Prototype 测试)
cargo test -p di-example

# 只运行 GB28181 相关测试(70+ 个)
cargo test -p tx_gb28181 -p tx_di_gb28181

许可证

MIT — 详见 LICENSE

Dependencies

~14–20MB
~273K SLoC