Nest.js Provider 提供者
Provider 是 Nest 的一个基本概念(后续我们称之为 提供者
)。 许多基本的 Nest 类可以被视为提供者 ——服务、存储库、工厂、助手等等。 提供者的主要思想是它可以作为依赖注入; 这意味着对象之间可以创建各种关系,并且“连接”对象实例的功能在很大程度上可以委托给 Nest 运行系统。
在上一章中,我们构建了一个简单的 CatsController
。 控制器应该处理 HTTP 请求并将更复杂的任务委托给提供者。 提供者是在模块中声明为 providers
的纯 JavaScript 类。
提示
: 由于 Nest 能够以更面向对象的方式设计和组织依赖项,因此我们强烈建议遵循 SOLID 原则。
服务
让我们从创建一个简单的 CatsService
开始。 该服务将负责数据存储和检索,并且设计为供 CatsController 使用,因此它是一个很好的候选者,可以定义为提供者。
cats.service.ts
import { Injectable } from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; @Injectable() export class CatsService { private readonly cats: Cat[] = []; create(cat: Cat) { this.cats.push(cat); } findAll(): Cat[] { return this.cats; } }
要使用 CLI 创建服务,只需执行下面的命令。
$ nest g service cats
我们的 CatsService
是一个具有一个属性和两个方法的基本类。 唯一的新特性是它使用了 @Injectable()
装饰器。 @Injectable() 装饰器附加元数据,它声明 CatsService 是一个可以由 Nest IoC 容器管理的类。 顺便说一句,这个例子也使用了一个 Cat 接口,它可能看起来像这样:
interfaces/cat.interface.ts
export interface Cat { name: string; age: number; breed: string; }
现在我们有了一个检索 cat 的服务类,让我们在 CatsController 中使用它:
cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common'; import { CreateCatDto } from './dto/create-cat.dto'; import { CatsService } from './cats.service'; import { Cat } from './interfaces/cat.interface'; @Controller('cats') export class CatsController { constructor(private catsService: CatsService) {} @Post() async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } @Get() async findAll(): Promise<Cat[]> { return this.catsService.findAll(); } }
CatsService
通过类构造函数注入。 注意 private
语法的使用。 这个简写允许我们在同一位置立即声明和初始化 catsService
成员。
依赖注入
Nest 是围绕通常称为依赖注入的强大设计模式构建的。 我们建议阅读 Angular 教程中有关此概念的精彩文章。
在 Nest 中,借助 TypeScript 功能,管理依赖关系非常容易,因为它们仅按类型解析。 在下面的示例中,Nest 将通过创建并返回 CatsService 的实例(或者,在单例的正常情况下,如果已经在其他地方请求过现有实例,则返回现有实例)来解析 catService。 此依赖项已解析并传递给控制器的构造函数(或分配给指定的属性):
constructor(private catsService: CatsService) {}
作用域
提供者通常具有与应用程序生命周期同步的生命周期(“scope”)。 当应用程序启动时,必须解决每个依赖项,因此必须实例化每个提供程序。 同样,当应用程序关闭时,每个提供程序都会被销毁。 但是,也有一些方法可以使我们提供者的生命周期成为请求范围。
自定义 Provider
Nest 有一个内置的控制反转(“IoC”)容器,可以解决提供者之间的关系。 这个特性是上述依赖注入特性的基础,但实际上比我们目前所描述的要强大得多。 有几种方法可以定义 Provider :我们可以使用类以及异步或同步工厂模式。
可选 Provider
有时,我们可能具有不一定必须解决的依赖关系。 例如,类可能依赖于配置对象,但如果没有传递,则应使用默认值。 在这种情况下,依赖项变为可选的,因为缺少配置 provider 程序不会导致错误。
要设置provider程序是可选的,需要在构造函数的签名中使用 @Optional()
装饰器。
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}
注意,在上面的示例中,我们使用了自定义 provider 程序,这就是我们包含 HTTP_OPTIONS
自定义令牌的原因。 前面的示例显示了基于构造函数的注入,通过构造函数中的类指示依赖关系。
基于属性的注入
到目前为止,我们使用的技术称为基于构造函数的注入,因为提供程序是通过构造函数方法注入的。 在某些非常特殊的情况下,基于属性的注入可能很有用。 例如,如果顶级类依赖于一个或多个提供者,那么通过在构造函数的子类中调用 super() 将它们一路向上传递可能非常乏味。 为了避免这种情况,我们可以在属性级别使用 @Inject()
装饰器。
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
@Inject('HTTP_OPTIONS')
private readonly httpClient: T;
}
警告
:如果你的类没有扩展另一个提供者,你应该总是更喜欢使用基于构造函数的注入。
注册 Provider
现在我们已经定义了一个提供者(CatsService),并且我们有了该服务的消费者(CatsController),我们需要向 Nest 注册该服务,以便它可以执行注入。 我们通过编辑我们的模块文件 (app.module.ts) 并将服务添加到 @Module()
装饰器的提供程序数组来做到这一点。
app.module.ts
import { Module } from '@nestjs/common'; import { CatsController } from './cats/cats.controller'; import { CatsService } from './cats/cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], }) export class AppModule {}
Nest 现在将能够解析 CatsController 类的依赖关系。
这就是我们的目录结构现在的样子:
手动实例化
到目前为止,我们已经讨论了 Nest 如何自动处理解决依赖关系的大部分细节。 在某些情况下,我们可能需要跳出内置的依赖注入系统并手动检索或实例化提供程序。 我们将在下面简要讨论两个这样的主题。
要获取现有实例或动态实例化提供程序,可以使用模块引用。
要在 bootstrap()
函数中获取提供程序(例如,对于没有控制器的独立应用程序,或在引导期间利用配置服务),可以参阅独立应用程序。