Nest 提供者与服务

提供者是 Nest 中的一个基本概念。提供者并不像模块(@Module()
)或者控制器(@Controller()
)一样,具有见名知意的实体类,它表现的更加广泛,在 Nest 中许多基本类可以被视为提供者,例如:服务(services)、存储库(repositories)、工厂(factories)、*助手(helpers)*等等。当定义上面的基本类之后,在模块中进行注册,就可以在 Nest 程序中使用了。
提供者的主要思想是它可以作为依赖项注入(Dependency Injection,DI),这意味着实例对象可以彼此之间建立各种关系,而对象实例的“连接”功能可以在很大程度上委托给 Nest 运行时系统(IOC 机制)。
接下来,会了解到:
- 创建服务
- 注入服务两种方式
- 提供者注册多种方式
- 可选提供者
- 循环依赖服务
服务的基本使用
控制器应该负责处理 HTTP 请求与响应,对于其中内部复杂的任务委托给提供者,也就是服务(Service)。
先创建一个服务,需要借助 @Injectable()
装饰器来定义一个 JavaScript 类,该类就会在 Nest 解析的时候,作为依赖性进行注入。
创建好服务之后,就可以在该服务提供各种能力。

如上图所示,定义了一个 TestSerive
服务,编写了四个方法,提供了增删改查的能力。
这就创建好了一个最简单的服务。
当服务创建好之后,就需要把它注册到 nest 程序中。该如何注册呢?
可以通过模块文件(test.module.ts
)并将服务添加到@Module()
装饰器的providers
数组中来实现。

当操作完这一步之后,就可以在其他服务中或者控制器中使用该服务(TestService)了。
当服务被创建完成并注册之后,就可以在控制器中使用了(在其他的服务中也能使用)

定义的 TestController
控制器采用了 restful 风格,在增删改查路由中分别调用了服务(TestService)中的不同方法,提供不同的能力。
调用服务的能力,很好理解;着重看一下服务的注入(也就是上面框选中的)。
服务注入是有两种形式的
构造函数注入
export class TestController {
constructor(private readonly testService: TestService) {}
}
属性注入
import { Inject } from "@nestjs/common";
export class TestController {
@Inject(TestService)
private readonly testService: TestService;
}
这两种注入方式表现和使用都是一样的。对于该怎么选择呢,就看自己的习惯。
但是存在一种特殊情况,就是存在类继承关系,并且父类的构造函数里面接受多个多个服务,那么子类继承时,调用 super()
方法,就需要传递多个服务,这种情况下采用构造函数注入就比较麻烦。这时候,父类采用属性注入的方式,那么子类就不用传递多个服务。
是不是对于构造函数注入写法感到困惑:为什么给函数的形参定义了类型之后,就是类的实例?
在 Nest 中,由于 TypeScript 的能力,管理依赖项变得非常容易,因为它们仅通过类型进行解析。在上面的示例中,Nest 将通过创建并返回一个
TestService
实例(或者在正常情况下,如果已经在其他地方请求过,则返回现有实例)来解析testService
。此依赖项将被解析并传递给控制器的构造函数(或分配给指定的属性)。
服务的大致使用流程就是这样,你懂了不?
provider 多种注册方式
@Module()
的 providers 属性接受值是一个数组,其数组里面的内容,却是可以写成多种形式(类、对象)。
useClass
在前面的实例中,在 test.module.ts
中注册服务。
@Module({
controllers: [TestController],
providers: [TestService], // 注册服务,接受的一个类
})
export class TestModule {}
上面 providers 的写法其实是一个简写

完整的写法是这样的,通过 provide 指定 token,通过 useClass 指定对象的类,Nest 会自动对它做实例化后用来注入。
服务的注入方式有两种,其实写法的具体含义是这样的:
// 构造函数注入
export class TestController {
constructor(private readonly testService: token) {} // token
}
// 属性注入
import { Inject } from "@nestjs/common";
export class TestController {
@Inject(token) // token
private readonly testService: TestService;
}
根据 token 找实例化对象。
其实 token 也可以是一个字符串
。

当 token 为字符串时,服务注入的写法也要改变。
@Controller("test")
export class TestController {
constructor(
// 构造函数注入
@Inject("test-service") private readonly testService: TestService
) {}
// 属性注入
@Inject("test-service")
private readonly testService1: TestService;
}
useValue
当 providers 数组中的元素项为对象时,也可以设置 useValue
属性,用来提供一些静态的数据。
就比如:

{
provide: 'test-useValue',
useValue: {
info: 'useValue-data',
},
},
使用 provide 指定 token,使用 useValue 属性注册静态数据{info: string}

@Inject('test-useValue')
private readonly testUseValue: { info: string; };
test-useValue
服务注入之后,然后在请求方法中打上断点,发送网络请求,就发现静态数据被注入成功。
useFactory
useValue 用于注入提供静态数据,使用 useFactory
就可以注入提供动态数据。

{
provide: 'test-useFactory',
useFactory: () => {
let name = 'copyer';
let age = 18;
// ...逻辑赋值操作(name, age)
return {
name,
age,
};
},
},
使用 provide 指定 token,使用 useFactory 函数来返回动态数据{name: string; age: number}
(在函数里面可以进行逻辑操作,进行赋值)

@Inject('test-useFactory')
private readonly testUseFactory: { name: string; age: number };
test-useFactory
服务注入之后,然后在请求方法中打上断点,发送网络请求,就发现动态数据被注入成功。
useFactory 还可以是异步的。

{
provide: 'test-useFactory-async',
useFactory: async () => {
const res = await new Promise<{ name: string; age: number }>(
(resolve) => {
setTimeout(() => resolve({ name: 'copyer', age: 18 }), 3000);
},
);
return res;
},
},
编写成一个异步函数 async,进行注册

发现成功注入了异步函数(useFactory)返回的数据。
Nest 会等拿到异步方法的结果之后再注入,就可以更加灵活的实现对象注入。
useFactory 不仅可以使用异步函数,而且还能使用其他的服务,真的强大。

@Module({
controllers: [TestController],
providers: [
TestService,
{
provide: "test-useValue",
useValue: {
info: "test-useValue-data",
},
},
{
provide: "test-useFactory-inject",
useFactory: (
testUseValue: { info: string },
testService: TestService
) => {
return {
info: testUseValue.info,
data: testService.create(),
};
},
inject: ["test-useValue", TestService],
},
],
})
export class TestModule {}
在 TestModule 注册了三个服务,在服务 test-useFactory-inject
服务中使用了前面两个服务,通过 inject
属性进行注入。

可以发现,各服务之间的数据也注入成功了。
useExisting
使用 useExisting 给已经存在的服务取一个新的服务名。

{
provide: 'test-new-useValue',
useExisting: 'test-useValue',
},
给 test-useValue
取了一个新的名称 test-new-useValue
,然后注入到控制器中使用,也可以获取到数据对象。
其实感觉 useExisting 没什么作用,或者说还不知道具体的使用场景。
可选提供者
当使用一个服务时,就必须先注册服务,不然 Nest 程序就运行不起来,报错。
但是会存在一种情况(以 useValue 为例),useValue 注册了一个配置对象,那么就可以在控制器或者其他服务中读取配置使用。但是如果没有配置对象,Nest 程序应该报错吗?不应该,因为没有读取到配置对象,就应该读取系统的默认配置对象。
那么这时候,就应该在注入 useValue 提供者的时候,设置为一个可选的,没有它,程序也能正常运行。

注释 test-useValue 服务

注入 test-useValue 时,就需要添加一个装饰器 @Optional()
,代表这个服务,可选的。
由于前面的注释,没有该服务,最后打印的错误信息:Error Services
,采用系统默认的配置对象。
循环引用服务
在模块一节中,知道了模块间的循环引用,使用 forwardRef
来进行解决的,先创建后关联。
其实服务中的循环引用也是一样,也是使用 forwardRef
来进行解决的。
// test.service.ts
import { Injectable } from "@nestjs/common";
import { Test1Service } from "./test1.service";
@Injectable()
export class TestService {
constructor(private readonly test1Service: Test1Service) {}
create() {
return this.test1Service.create() + "test-service"; // 使用 test1Service
}
}
// test1.service.ts
import { Injectable } from "@nestjs/common";
import { TestService } from "./test.service";
@Injectable()
export class Test1Service {
constructor(private readonly testService: TestService) {}
create() {
return this.testService.create() + "test1-service"; // 使用 testService
}
}
// test.module.ts
@Module({
controllers: [TestController],
providers: [TestService, Test1Service], // 注册两个服务
})
export class TestModule {}
启动项目,你就会发现报错。报错的原因就是产生了循环依赖,你找不到我,我找不到你,为 undefined。
解决方法

项目就能成功的运行了。
总结
- 服务的基本使用,使用
@Injectable()
来定义一个服务,在模块@Module()
的 providers 属性中注册。 - 注入方式有两种:构造函数注入和属性注入
@Inject()
。 - providers 的注册有多种方式,最常用的就是直接注册一个类,这是一种简写,其实本质上是使用的
useClass
的方法。还能使用useValue
注册静态数据,useFactory
注册动态数据。 - useFactory 功能比较强大,不仅能使用异步的形式,也能在其内部注册其他的服务。
- 使用
@Optional()
来定义可选服务 - 跟模块一样,可以使用 forwardRef 来解决循环依赖服务。
转载自:https://juejin.cn/post/7374677514537254939