跳到主要内容

业务逻辑与依赖注入

zenweb 最核心的设计理念 依赖注入 功能,设计上参考了 Spring 方案并做了一些调整

定义一个最简单的业务类

src/service/user_service.ts
// 定义并导出类
export class UserService {
getByUsername(username: string) {
return "You name is: " + username;
}
}

在控制器中使用业务类实例

src/controller/user.ts
export class UserController {
@mapping()
getByService(userService: UserService) { // 只需要在控制器方法参数中标识需要使用的类即可
return userService.getByUsername("Bob");
}
}

在业务类中注入业务类

src/service/auth_service.ts
import { component } from "zenweb";
import { UserService } from "./user_service";

@component // 使用 component 注解可以注入依赖类
export class AuthService {
constructor(
private userService: UserService,
) {}

login(username: string) {
return this.userService.getByUsername(username);
}
}

作用域级别

类作用域是指一个类的实例所处的上下文生命周期

zenweb 注入类的作用域一个分为一下三种:

  • singleton: 单例模式,对象只会初始化一次并一直存活在整个应用生命周期中
    • 此级别下对象不可注入 request 级别的对象
  • request: 请求模式,当前请求中只会被初始化一次,同一个请求中上下文中共享同一个实例对象
    • 被标记为 request 作用域的对象不能注入到 singleton 中
    • 默认级别
  • prototype: 原型模式,每次注入时都会被初始化,每一个注入对象都是新的
    • 因为每次都新建一个实例,通常情况下不使用
    • 任何级别都可以注入此级别的对象
    • 控制器的默认级别
与 Spring 的区别

默认作用域级别为 request,而并非 singleton, 因为在 web 项目中这更常用,也避免错误的使用 singleton 造成共享变量污染。

修改类的作用域

src/service/db_service.ts
import { component } from "zenweb";

// 标记类为单例类型
@component("singleton")
export class DbService {
// 通常来说数据库连接只需要一次即可,避免每次请求都连接数据浪费资源
conn = new DatabaseConnection();

query(sql: string) {
return this.conn.query(sql);
}
}

在非业务类中取得类实例

我们知道在业务类中可以通过 @component@inject 自动注入类实例

如果在 中间件 中,这种非业务类的方法中如何获得一个类实例呢?

其实在 Context 上下文对象中存在一个 injecter 属性,可以通过他来取得需要的对象实例。

function someMiddleware(): Middleware {
return async function (ctx, next) {
// 注意这里一定要使用 await
const someService = await ctx.injecter.getInstance(SomeService);
// ...
return next();
}
}

类的异步初始化

依赖注入有以下几点需要注意:

  • 一般情况下推荐使用 @component 和类构造器 constructor() 来注入业务类的依赖,这也是最推荐的方式

  • @inject 依赖注入会先初始化一个类后再设置依赖属性到实例中

    • 这样就会造成一个问题,在 constructor() 构造器中无法调用到有效的依赖属性
  • 一个标准的 js class 是不可以把类构造器 constructor() 声明为异步的

    • 如果你需要在构造器内 await 一些方法是不成立的,如果需要异步初始化,可以在类中声明一个方法并使用 @init 装饰器来告诉注入器此类需要初始化操作。
import { component, init } from "zenweb";

class TargetClass {}

@component
export class SomeClass {
constructor(
private target: TargetClass,
) {}

constructor() {
console.log('constructor: %o', this.target); // 构造器里无法获得依赖类的实例
// output: constructor: undefined
}

// 使用 @init 装饰器来指定类方法为初始化方法
// 在类需要初始化时依赖注入器会自动调用此方法
@init
initMethod() {
console.log('initMethod: %o', this.target); // 此时已经可以取得依赖类的实例
// output: initMethod: TargetClass {}
}

// 你甚至可以定义多个初始化方法
// 并且可以使用异步方法
@init
async otherInitMethod() {
await Promise();
}

// 你也可以像使用控制器方法那样,使用方法参数注入依赖
// 例如这个依赖我只想要在初始化方法内使用,不想暴露出来
@init
likeControllerMethod(target: TargetClass) {
}
}