2 个版本

0.1.1 2023 年 1 月 26 日
0.1.0 2023 年 1 月 25 日

#26 in #black-box


2 个crate中使用(通过blackbox_core

MIT 协议

15KB
139 行代码(不含注释)

BlackBox DI

Rust 的依赖注入

安装

在项目目录下运行以下 Cargo 命令

cargo add blackbox_di

或添加以下行到你的 Cargo.toml 文件中

blackbox_di = "0.1"

使用方法

提供者创建

使用 #[interface] 注解接口

#[interface]
trait IService {
  fn call(&self);
}

使用 #[injectable] 注解服务结构

#[injectable]
struct Service {}

使用 #[implements] 注解 impl 块

#[implements]
impl IService for Service {
  fn call() {
    println!("Service calling");
  }
}

模块创建

使用 #[module] 注解模块结构并指定 Service 结构作为 provider

#[module]
struct RootModule {
  #[provider]
  service: Service
}

容器创建

#[launch]
async fn launch() {
    let app = build::<RootModule>(BuildParams::default()).await;

    let service = app
      .get_by_token::<Service>(&get_token::<Service>)
      .unwrap();

    // short (equivalent to above)
    let service = app.get::<Service>().unwrap();
    service.call();
}

注入引用

按类型注入

为可注入依赖指定 #[inject]

#[injectable]
struct Repo {}

#[injectable]
struct Service {
  #[inject]
  repo: Ref<Repo>
}

别忘了在 RootModule 模块中指定 Repo

#[module]
struct RootModule {
  #[provider]
  repo: Repo

  #[provider]
  service: Service
}

按令牌注入

您可以用令牌代替类型

#[injectable]
struct Repo {}

#[injectable]
struct Service {
  #[inject("REPO_TOKEN")]
  repo: Ref<Repo>
}

然后

#[module]
struct RootModule {
  #[provider("REPO_TOKEN")]
  repo: Repo

  #[provider]
  service: Service
}

或者使用常量作为令牌

const REPO_TOKEN: &str = "REPO_TOKEN";

#[injectable]
struct Repo {}

#[injectable]
struct Service {
  #[inject(REPO_TOKEN)]
  repo: Ref<Repo>
}

#[module]
struct RootModule {
  #[provider(REPO_TOKEN)]
  repo: Repo

  #[provider]
  service: Service
}

使用接口

您还可以使用接口进行可注入依赖项的注入

const REPO_TOKEN: &str = "REPO_TOKEN";

#[interface]
trait IRepo {}

#[injectable]
struct Repo {}

#[implements]
impl IRepo for Repo {}

#[injectable]
struct Service {
  #[inject(REPO_TOKEN)]
  repo: Ref<dyn IRepo>
}

#[module]
struct RootModule {
  #[provider(REPO_TOKEN)]
  repo: Repo

  #[provider]
  service: Service
}

或者直接使用现有接口的实现

#[interface]
trait IRepo {}

#[injectable]
struct Repo {}

#[implements]
impl IRepo for Repo {}

#[injectable]
struct Service {
  #[inject(use Repo)]
  repo: Ref<dyn IRepo>
}

#[module]
struct RootModule {
  #[provider]
  repo: Repo

  #[provider]
  service: Service
}

工厂

如果一个服务有非注入依赖项

#[injectable]
struct Service {
  #[inject]
  repo: Ref<Repo>

  greeting: String
}

您应该指定一个工厂函数

#[implements]
impl Service {
  #[factory]
  fn new(repo: Ref<Repo>) -> Service {
    Service {
      repo, 
      greeting: String::from("Hello")
    } 
  }
}

或者对于接口

#[injectable]
struct Service {
  #[inject(use Repo)]
  repo: Ref<dyn IRepo>

  greeting: String
}

#[implements]
impl Service {
  #[factory]
  fn new(repo: Ref<dyn IRepo>) -> Service {
    Service {
      repo, 
      greeting: String::from("Hello")
    } 
  }
}

带有 non-injectable 依赖的可注入服务必须具有 factory 函数。

为了有可变非注入依赖项,您需要使用 RefMut<...>

#[injectable]
struct Service {
  #[inject(use Repo)]
  repo: Ref<dyn IRepo>

  greeting: RefMut<String>
}

#[implements]
impl Service {
  #[factory]
  fn new(repo: Ref<dyn Repo>) -> Service {
    Service {
      repo, 
      greeting: RefMut::new(String::from("Hello"))
    } 
  }

  fn set_greeting(&self, msg: String) {
    *self.greeting.as_mut() = msg;
  }

  fn print_greeting(&self) {
    println!("{}", self.greeting.as_ref());
  }
}

模块

您可以指定多个模块并将它们导入

#[module]
struct UserModule {
  #[provider]
  user_service: UserService,
}

#[module]
struct RootModule {
  #[import]
  user_module: UserModule
}

要使用导入模块中的提供者,您应该将这些提供者指定为 exported

#[module]
struct UserModule {
  #[provider]
  #[export]
  user_service: UserService,
}

此外,您还可以将您的模块指定为 global,这样您就不必直接导入它们。只需在 root 模块中指定它们即可

#[module]
#[global]
struct UserModule {
  #[provider]
  #[export]
  user_service: UserService,
}

#[module]
struct AccountModule {
  #[provider]
  #[export]
  account_service: AccountService,
}


#[module]
struct RootModule {
  #[import]
  user_module: UserModule

  #[import]
  account_module: AccountModule
}

依赖循环

在模块导入时使用 Lazy 来解决依赖循环

#[module]
struct UserModule {
  #[import]
  account_module: Lazy<AccountService>,

  #[provider]
  #[export]
  user_service: UserService,
}

#[module]
struct AccountModule {
  #[import]
  user_module: Lazy<UserModule>,
  
  #[provider]
  #[export]
  account_service: AccountService,
}


#[module]
struct RootModule {
  #[import]
  user_module: UserModule

  #[import]
  account_module: AccountModule
}

生命周期事件

当容器完全初始化后,系统触发事件 on_module_init

#[implements]
impl OnModuleInit for Service {
  async fn on_module_init(&self) {
    ...
  }
}

on_module_destroy

#[implements]
impl OnModuleDestroy for Service {
  async fn on_module_destroy(&self) {
    ...
  }
}

日志记录器

日志记录器用于显示关于应用程序构建的信息。要使用自定义日志记录器,实现 ILogger 特性

pub trait ILogger {
    fn log<'a>(&self, level: LogLevel, msg: &'a str);
    fn log_with_ctx<'a>(&self, level: LogLevel, msg: &'a str, ctx: &'a str);
    fn set_context<'a>(&self, ctx: &'a str);
    fn get_context(&self) -> String;
}

然后更改构建参数


let app = build::<RootModule>(
  BuildParams::default().buffer_logs()
).await;

let custom_logger = app.get::<CustomLogger>().unwrap();

app.use_logger(custom_logger.cast::<dyn ILogger>().unwrap());

许可证

BlackBox DI 的许可证为

依赖项

~2MB
~45K SLoC