跳到主要内容

业务逻辑与依赖注入

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 { inject } from "zenweb";
import { UserService } from "./user_service";

export class AuthService {
// 使用 @inject 装饰器即可导入依赖类实例
@inject 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 { scope } from "zenweb";

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

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

在非业务类中取得类实例

我们知道在业务类中可以通过 @inject 来取得一个类的实例

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

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

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

类的异步初始化

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

  • 依赖注入初始化一个类的顺序是先 new class 后设置依赖属性到实例中
    • 这样就会造成一个问题,在 constructor() 构造器中无法调用到有效的依赖属性
  • 一个标准的 js class 是不可以把类构造器 constructor() 声明为异步的
    • 如果你需要在构造器内 await 一些方法是不成立的
  • 默认情况下类构造器不能有参数
    • 因为在依赖注入时是不可以指定类参数的, 你不能像传统使用一个类 new SomeClass(...someval) 这样去指定类参数,因为类的生命周期是依赖注入器托管的。 除非使用 @factory({ create }) 装饰器自定义类的创建。

zenweb inject 专门提供了一个装饰器方法 @init 用于解决此类问题。

import { inject, init } from "zenweb";

class TargetClass {}

export class SomeClass {
@inject 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) {
}
}

自定义类的创建

如果类存在构造器参数如何做呢?

import { inject, init } from "zenweb";

// 可以使用 @factory 装饰器的 create 方法来自定义类的构建
@factory({
create() {
return new Db('localhost', 3306);
}
})
export class Db {
constructor(host: string, port: number) {}
}