一、Provider Overview
Provider 是 Nestjs 中的一个基本概念,很多 Nest 类,包括 services, repositories, factories, helpers 等等都可以被视为 Provider 并作为依赖注入。这意味着多个对象彼此之间可以创建各种关系,且“连接”这些对象的功能可以委托给 Nest 运行时系统而无需开发者自己处理。
示例:
以下是一个包含 数据存储 和 检索 功能的 CatsService 类,它是为了提供给 CatsController 使用而被设计出来的,因此,很适合被定义为 Provider。
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsService {
constructor() {
this.cats = [];
}
create(cat) {
this.cats.push(cat);
}
findAll() {
return this.cats;
}
}
为了让普通的类能够被 Nest 识别为 Provider,我们需要给这个类添加 @Injectable() 装饰器来绑定用于声明 CatsService 能够被 Nest 的 Ioc 容器管理的元数据。
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 {
// 在 TypeScript 中,参数属性是一种特殊的语法,它允许你在一个地方定义并初始化一个类的成员。
// 具体来说,你可以在构造函数的参数前面添加 public、private、protected 或 readonly 修饰符,TypeScript 就会自动为你创建并初始化一个同名的类成员
// private 可以让我们同时完成 CatsService 的声明和初始化的工作
constructor(private catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
上述内容中,我们已经定义了一个 CatsService 和一个 CatsController,此时我们还需要在对应的模块中注册 CatsService 让 Nest 在运行时能够执行注入操作。
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
至此,我们已经初步了解到一个 Nest 功能模块的基本目录结构:
src
-- cats
-- dto
-- create-cat.dto.ts
-- interfaces
-- cat.interface.ts
-- cats.controller.ts
-- cats.service.ts
-- app.module.ts
-- main.ts
二、Custom Providers
(一) DI fundamentals
依赖注入dependency injection是一种 IoC(控制反转)技术,你可以将依赖的实例化过程移交给 IoC 容器(Nest 的运行时系统),无须开发者在代码中手动执行这些操作。
先回顾下第一节的内容:
-
@Injectable() 装饰器用于将 CatsService 声明为可以被 Nest IoC 容器管理的类
-
通过 constructor(private catsService: CatsService) 声明依赖注入
-
将 CatsService 令牌与 './cats.service' 文件中 CatsService 类建立联系
当 Nest IoC 容器实例化 CatsController 时,首先会查找它的每一个依赖。比如,当发现有 CatsService 依赖时,会根据上述的三个注册步骤进行查找并返回 CatsService 类进行实例化【默认使用单例模式】,创建实例后会将其缓存,如果其他模块引用时发现该实例在缓存中已经存在,则返回缓存池的实例,无需再次执行实例化操作。
此外,Nest 解析代码依赖的过程是传递性的,在上述示例中,如果 CatsService 自身也有依赖项,这些依赖也会按照上述过程进行处理,从而保证依赖关系图能够按照正确的顺序【自下而上】处理
(二) None-class-based Provider tokens
有时,我们可能会希望用更灵活的方式,比如 String | Symbol 作为依赖注入的 token
import { connection } from './connection';
@Module({
providers: [
/*
providers: [CatsService]
相当于
{
provide: CatsService,
useClass: CatsService
}
useValue 语法可以用来注入常量、将外部库注入Nest或者使用模拟对象替换具体实现
*/
{
provide: 'CONNECTION',
useValue: connection,
},
],
})
export class AppModule {}
// 对于上述 None-class-based Provider,需要使用 @Inject(token) 来注入依赖
@Injectable()
export class CatsRepository {
constructor(@Inject('CONNECTION') connection: Connection) {}
}
(三) Factory Providers: useFactory
useFactory支持动态创建 provider,接受使用者传入的参数,并将函数的返回结果作为最终注入的依赖
inject属性接收一个 provider 数组,并在实例化过程中将其中的元素作为参数传递给工厂函数
const connectionProvider = {
provide: 'CONNECTION',
useFactory: (optionsProvider: OptionsProvider, optionalProvider?: string) => {
const options = optionsProvider.get();
return new DatabaseConnection(options);
},
inject: [OptionsProvider, { token: 'SomeOptionalProvider', optional: true }],
// _____________/ __________________/
// This provider The provider with this
// is mandatory. token can resolve to `undefined`.
};
@Module({
providers: [
connectionProvider,
OptionsProvider,
// { provide: 'SomeOptionalProvider', useValue: 'anything' },
],
})
export class AppModule {}
(四) Alias Providers: useExisting
useExisting可以为存在的 provider 创建一个别名,让我们能使用两种方式访问到同一个 provider 实例
@Injectable()
class LoggerService {
/* implementation details */
}
const loggerAliasProvider = {
provide: 'AliasedLoggerService',
useExisting: LoggerService,
};
@Module({
providers: [LoggerService, loggerAliasProvider],
})
export class AppModule {}
(五) Asynchronous Providers
当我们希望程序在一个或多个异步任务执行完毕后启动,比如连接数据库等,可以在 useFactory 中使用 async/await,Nest 会等待任务执行完成后再实例化依赖该 provider 的类
{
provide: 'ASYNC_CONNECTION',
useFactory: async () => {
const connection = await createConnection(options);
return connection;
}
}