业务逻辑与依赖注入
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) {
}
}