Providers是NestJS的一个基本概念,可以让服务、存储库、工厂、helpers等等作为提供服务的一方进行依赖注入等操作,这也是我觉得Nest比较好开发和维护的原因。
在上一章中,我们构建了一个简单的CatsController控制器处理HTTP请求,并将更复杂的任务(业务逻辑,表更新等)委托给Providers。Providers是在Module中声明为Providers的纯JavaScript类。
依赖注入和控制反转
首先希望大家了解下依赖注入和控制反转,有不知道这个概念的朋友们请看浅谈控制反转与依赖注入,这篇文章通俗易懂。
Services
- Service主要是来做业务的逻辑,比如存储和检索。它可以作为一个
Providers提供给Controllers等。 - 用之前章节的方法,创建一个工程,再创建Cats的module和Controller,然后输入以下命令,来创建一个Service
# 生成service文件,并且不生成测试文件
nest g s cats --no-spec
- 在下图位置会生成一个Service文件。
- 再创建一个Interfaces文件夹和
cats.interface.ts文件,代码如下
export interface Cat {
name: string;
age: number;
breed: string;
}
- Service代码如下,需要用
@Injectable装饰器来装饰,它声明CatsService是一个可以由Nest IoC容器管理的类。注:Ioc是控制反转的缩写。
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;
}
}
- 这样在
Controller里面就可以通过注入的方式来调用Service,请注意构造函数注入时要加上private。
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 {
// 没有通过New来创建一个实例而是通过构造函数传入参数的方法来注入Service对象
constructor(private catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
// 通过this来访问注入的对象
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
// 通过this来访问注入的对象
return this.catsService.findAll();
}
}
依赖注入
Nest是围绕强设计模式构建的,通常称为依赖注入。
在Nest中,得益于TypeScript功能,管理依赖关系非常容易,因为它们仅通过类型来解析。在下面的示例中,Nest将通过创建并返回catsService的实例来解析catsService(或者,在正常情况下,如果已经在其他地方请求了现有实例,则返回该实例)。此依赖项被解析并传递给控制器的构造函数(或分配给指定的属性):
constructor(private catsService: CatsService) {}
作用域
提供程序通常具有与应用程序生命周期同步的生命周期。当应用程序启动时,必须解析每个依赖项,因此必须实例化每个提供程序。同样,当应用程序关闭时,每个提供程序都将被销毁。然而,也有一些方法可以使您的提供者生存期请求具有作用域。您可以在这里阅读有关这些技术的更多信息。
自定义 providers
Nest有一个内置的控制反转(“IoC”)容器,用于解决提供者之间的关系。这一特性是上述依赖注入特性的基础,但实际上比我们目前所描述的功能强大得多。定义提供程序有几种方法:可以使用纯值、类以及异步或同步工厂。此处提供了更多示例。
可选 providers
有时,您可能有不一定要解决的依赖关系。例如,您的类可能依赖于配置对象,但如果没有传递,则应使用默认值。在这种情况下,依赖项变得可选,因为缺少配置提供程序不会导致错误或者异常。
要表示提供程序是可选的,请在构造函数的签名中使用@optional()装饰器
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
// 可选参数
constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}
请注意,在上面的示例中,我们使用的是自定义Providers,这就是我们包含HTTP_OPTIONS自定义令牌的原因。前面的示例显示了基于构造函数的注入,通过构造函数中的类指示依赖关系。在此处阅读有关自定义提供程序及其相关令牌的更多信息。
基于属性的注入
到目前为止,我们使用的技术称为基于构造函数的注入,因为提供程序是通过构造函数方法注入的。在某些非常特殊的情况下,基于属性的注入可能很有用。例如,如果您的顶级类依赖于一个或多个提供程序,那么通过在构造函数的子类中调用super()将它们一直传递给上一级是非常乏味的。为了避免这种情况,可以在属性级别使用@Inject()修饰符。
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
@Inject('HTTP_OPTIONS')
private readonly httpClient: T;
}
警告:如果您的类没有扩展另一个Providers,您应该始终使用基于构造函数的注入。
Provider 注册
现在我们已经定义了一个Providers(CatsService),并且我们有了该服务的使用者(CatsController),我们需要向Nest注册该服务,以便它能够执行注入。我们通过编辑模块文件(cats.module.ts)并将服务添加到@module()装饰器的providers数组中来实现这一点。
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService]
})
export class CatsModule {}
手动实例化
到目前为止,我们已经讨论了Nest如何自动处理解决依赖关系的大部分细节。在某些情况下,您可能需要跳出内置的依赖注入系统,手动检索或实例化提供程序。我们将在下面简要讨论两个这样的主题。
要获取现有实例或动态实例化提供程序,可以使用Module引用。
要在bootstrap()函数中获取提供程序(例如,对于没有控制器的独立应用程序,或在引导过程中使用配置服务),请参阅独立应用程序。