< 返回版块

ZihanType 发表于 2023-08-09 16:38

Tags:依赖注入, dependency-injection, DI, IOC

Rudi

Rudi,一个开箱即用的依赖注入框架。

Rust 代码写多了之后,就想有个依赖注入框架,让代码写起来轻松一点。在社区中找了一些现有的依赖注入框架,但是觉得都不好用,看着就很麻烦,于是就自己写了一个。

我对依赖注入框架的手感,都是来自于 SpringBoot,加些注解,然后一行代码启动,就可以用了。所以我希望 Rudi 也能做到这样的体验,突出一个开箱即用,懒人福音。

刚开始不知道怎么解决自动扫描,自动注册的问题,就参考 Koin的 API,写了一套基于函数和模块,手动注册的 API。后来发现 inventory 这个库,可以解决自动注册的问题,于是又添加了属性宏,实现了自动注册。

一个简单的例子

use rudi::{Context, Singleton, Transient};

// 把 `fn(cx) -> A { A }` 注册为 `A` 的构造函数
#[derive(Debug)]
#[Transient]
struct A;

#[derive(Debug)]
struct B(A);

// 把 `fn(cx) -> B { B(cx.resolve::<A>()) }` 注册为 `B` 的构造函数
#[Transient]
impl B {
    fn new(a: A) -> B {
        B(a)
    }
}

// 把 `fn(cx) -> () { run(cx.resolve::<B>()) }` 注册为 `()` 的构造函数
#[Singleton]
fn run(b: B) {
    println!("{:?}", b);
}

fn main() {
    // 把标记了 `#[Singleton]` 和 `#[Transient]` 的类型和函数,自动注册到 `Context` 中
    let mut cx = Context::auto_register();

    // 从 `Context` 中获取 `()` 的实例,这样会调用 `run` 函数
    // 这个写法等同于 `cx.resolve::<()>();`
    cx.resolve()
}

可以看到 run 函数,是一个接收很多参数,返回一个 () 的函数,这个函数成为整个程序的入口,想要什么依赖,就写在这个函数的参数就行了,main 函数中,只需要调用 cx.resolve() 就可以了。

除了上面这个简单的例子,Rudi 还支持:

  • 注册异步函数。(必须支持!!)
  • 手动注册和自动注册。(自动注册依赖于 inventory,如果是 inventory 不支持的平台,比如 WebAssembly,就只能手动注册了)
  • 轻松绑定 trait 对象。(其他很多依赖注入框架,在处理 trait 的时候,太拧巴了,在 Rudi 中,不关心 trait,你给我一个 trait 对象就好)
  • 用类型和名称区分不同的实例。(有时候,我们需要多个实例,但是类型都是一样的,这时候,就可以用名称来区分了)
  • 支持泛型。(但是只能指定类型后手动注册,这很好理解,一个不确定的类型是不可能注册到容器中的)

一个更复杂的例子

use std::{fmt::Debug, rc::Rc};

use rudi::{Context, Singleton, Transient};

// 注册一个异步函数,作为 `i32` 的构造函数,并指定此 `i32` 实例的名称
#[Singleton(name = "number")]
async fn number() -> i32 {
    42
}

#[derive(Debug, Clone)]
#[Singleton(async_constructor, name = "foo")] // 注册一个异步构造函数,并指定此 `Foo` 实例的名称
struct Foo {
    #[di("number")] // 指定此字段的名称,不仅是要一个 `i32` 类型,且名称必须为 `number` 的实例
    number: i32,
}

#[derive(Debug)]
struct Bar(Foo);

impl Bar {
    fn into_debug(self) -> Rc<dyn Debug> {
        Rc::new(self)
    }
}

#[Transient(binds = [Self::into_debug])] // 绑定一个 `Rc<dyn Debug>` 类型到 `Bar` 上,`Rc<dyn Debug>` 会和 `Bar` 一起注入
impl Bar {
    async fn new(#[di("foo")] f: Foo) -> Bar { // 注册一个异步构造函数,且指定参数名称
        Bar(f)
    }
}

#[Singleton]
async fn run(bar: Bar, debug: Rc<dyn Debug>, #[di("foo")] f: Foo) {
    println!("{:?}", bar);
    assert_eq!(format!("{:?}", bar), format!("{:?}", debug));
    assert_eq!(format!("{:?}", bar.0.number), format!("{:?}", f.number));
}

#[tokio::main]
async fn main() {
    let mut cx = Context::auto_register();

    cx.resolve_async().await
}

更多的信息可以在仓库上看到。

Rudi

欢迎各位使用,有问题欢迎提 issue,如果觉得好用,可以点个 star。

评论区

写评论
作者 ZihanType 2023-08-10 09:18

谢谢

--
👇
jellybobbin: 赞一个,已star

--
👇
ZihanType: 还是指定名称。

use rudi::{Context, Singleton};

#[Singleton(name = "init_log")]
fn init_log() {}

#[Singleton(name = "migrator")]
fn migrate() {}

#[Singleton]
fn run(#[di("init_log")] (): (), #[di("migrator")] (): ()) {
    println!("Hello")
}

fn main() {
    let mut cx = Context::auto_register();

    // `run` 函数最好不要指定名称,用默认的,如果指定了名称,那么可以通过名称来获取
    // 实际上,`cx.resolve()` 就是 `cx.resolve_with_name("")`
    cx.resolve_with_name("")
}

--
👇
jellybobbin: 有多个 () 的函数呢

xbitlabs 2023-08-10 00:16

感觉没有必要

Nayaka 2023-08-09 23:05

我感觉怎么变得更麻烦了,SpringBoot害人不浅啊[doge]

github.com/shanliu/lsys 2023-08-09 22:47

把actix-web的data拿出来用就好了

pama 2023-08-09 20:07

有很多example的。

--
👇
gorust21: 我觉得用处不大

pama 2023-08-09 20:04

赞一个,已star

--
👇
ZihanType: 还是指定名称。

use rudi::{Context, Singleton};

#[Singleton(name = "init_log")]
fn init_log() {}

#[Singleton(name = "migrator")]
fn migrate() {}

#[Singleton]
fn run(#[di("init_log")] (): (), #[di("migrator")] (): ()) {
    println!("Hello")
}

fn main() {
    let mut cx = Context::auto_register();

    // `run` 函数最好不要指定名称,用默认的,如果指定了名称,那么可以通过名称来获取
    // 实际上,`cx.resolve()` 就是 `cx.resolve_with_name("")`
    cx.resolve_with_name("")
}

--
👇
jellybobbin: 有多个 () 的函数呢

gorust21 2023-08-09 19:39

我觉得用处不大

作者 ZihanType 2023-08-09 17:55

还是指定名称。

use rudi::{Context, Singleton};

#[Singleton(name = "init_log")]
fn init_log() {}

#[Singleton(name = "migrator")]
fn migrate() {}

#[Singleton]
fn run(#[di("init_log")] (): (), #[di("migrator")] (): ()) {
    println!("Hello")
}

fn main() {
    let mut cx = Context::auto_register();

    // `run` 函数最好不要指定名称,用默认的,如果指定了名称,那么可以通过名称来获取
    // 实际上,`cx.resolve()` 就是 `cx.resolve_with_name("")`
    cx.resolve_with_name("")
}

--
👇
jellybobbin: 有多个 () 的函数呢

pama 2023-08-09 17:42

有多个 () 的函数呢

1 共 9 条评论, 1 页