Provider是Nest实现IoC(控制反转 Inversion)、DI(Dependency Injection)的关键之一。Nest会在@Module
注解(装饰器)下注入对应的Provider至IoC容器中,在源码下我们可以看到@Module
实际上是执行了@SetMetaData
的操作,依次注入对应的Provider。
Providers
1. ClassProvider
最为常见的便是Service
层,以类为基础通过@Injectable装饰器来注册的Provider。
// AppService.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
// AppModule.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
实际上providers: [AppService]
是一种缩写的写法。Provider的组成分为Token
与Value
,Token代表的便是在其他容器中注入时使用@Inject
的键名(可以是变量<引用>或字符串),而useClass指定的类会自动实例化后注入到容器中。
上述的Class
形式的Provider中的Token与Value一致,因此可以直接使用Class
的构造器引用简写。完整的形式为
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: AppService,
useClass: AppService,
},
],
})
export class AppModule {}
ext. 全局变量作为Token
尝试将一个全局变量作为Provider的Token,此处的变量可以是任意类型(e.g. 字符串、函数、类...)此处以函数为例,更为直观。
// global.ts
export const GLOBAL_APP_SERVICE = () => void 0;
// appModule.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { GLOBAL_APP_SERVICE } from './global';
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: GLOBAL_APP_SERVICE,
useClass: AppService,
},
],
})
export class AppModule {}
// appController.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
import { GLOBAL_APP_SERVICE } from './global';
@Controller()
export class AppController {
constructor(
@Inject(GLOBAL_APP_SERVICE) private readonly appService: AppService,
) {}
/* // or you can use the following syntax
@Inject(GLOBAL_APP_SERVICE)
private appService: AppService; */
@Get()
getHello(): string {
return this.appService.getHello();
}
}
仍然能得到正确的响应结果。可以猜测NestJS中的容器注册类似于HashMap
,通过类似map.get
的方式得到一个结果。
2. ValueProvider
ValueProvider
作为Provider的一种方式,可以将变量/字符串/数字等注入到IoC容器中去。经过测试验证,useValue
仅支持静态属性的注入,而不支持通过函数形式进行动态获取。
e.g. 以字符串为例
// AppModule.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { GLOBAL_APP_SERVICE } from './global';
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: GLOBAL_APP_SERVICE,
useClass: AppService,
},
{
provide: 'GLOBAL_APP_KEY',
useValue: 'This is a global app key',
},
],
})
export class AppModule {}
// AppController.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
import { GLOBAL_APP_SERVICE } from './global';
@Controller()
export class AppController {
constructor(
@Inject(GLOBAL_APP_SERVICE) private readonly appService: AppService,
) {}
/* // or you can use the following syntax
@Inject(GLOBAL_APP_SERVICE)
private appService: AppService; */
@Inject('GLOBAL_APP_KEY')
private globalAppKey: string;
@Get()
getHello(): string {
return this.globalAppKey;
}
}
e.g. 函数形式测试
// partial code here
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: 'GLOBAL_APP_KEY',
useValue() {
return 'anything value will be return here';
},
},
],
})
3. FactoryProvider
FactoryProvider
是一种函数形式的动态注入的方式,支持异步、支持注入IoC容器已有的Provider
。
基本使用
// partial code.
const configProvider: FactoryProvider = {
provide: 'config',
useFactory: () => ({ apiUrl: 'http://localhost:3000', timeout: 3000 }),
};
@Module({
imports: [],
controllers: [AppController],
providers: [
AppService,
{
provide: 'GLOBAL_APP_KEY',
useValue: 'This is a global app key',
},
configProvider,
ExternalServiceService,
],
})
export class AppModule {}
异步形式
FactoryProvider
支持异步形式的工厂方法,Nest会等待异步结束后将普通值(非Promise)注入到容器中。
const dataBaseDiffByEnvProvider: FactoryProvider = {
provide: 'DATABASE_CONNECTION',
useFactory: async () => {
const mockConnectDatabase = () =>
new Promise((resolve) => setTimeout(resolve, 3000));
await mockConnectDatabase();
if (process.env.NODE_ENV === 'development') {
return 'DATABASE_CONNECTION_DEVELOPMENT';
}
return 'DATABASE_CONNECTION_PRODUCTION';
},
};
注入Provider
FactoryProvider同样支持依赖注入,通过inject
属性来传递所需的依赖项。在useFactory
方法中,将根据inject
属性所指定的顺序为工厂函数提供相应的参数值。
const injectProvider: FactoryProvider = {
provide: 'inject',
useFactory: (appService: AppService) => {
return appService.getHello();
},
inject: [GLOBAL_APP_SERVICE],
};
4. ExistingProvider
ExistingProvider
是为已经存在容器中的Provider
创建一个别名。(通过官方的代码,ExistingProvider一般用于版本更新时废弃了某个功能类
,通过ExistingProvider
给新的功能类
加上废弃类
的别名,以兼容两个版本。
const existingProvider: ExistingProvider = {
provide: 'APP_SERVICE',
useExisting: 'GLOBAL_APP_SERVICE',
};
关于@Injectable
NestJS 的 IoC 容器会优先基于类的类型信息来解析依赖,而不是严格依赖于 @Injectable()
装饰器 。因此会发现可能没有@Injectable
的情况下,一个ClassProvider仍然被创建到IoC容器中且能够正常的注入到容器中。
不声明@Injectable 使用@Inject注入
不声明@Injectable
、在对应Module
添加了Provider的前提下,通过@Inject
进行实例注入是可行的。(注意:可行的前提是没有声明@Injectable
的Provider并没有使用构造器注入)
// ExternalServiceService.ts
import { Injectable } from '@nestjs/common';
// @Injectable()
export class ExternalServiceService {
getHello(): string {
return 'Hello from External Service!';
}
}
// AppService.ts
import { Inject, Injectable } from '@nestjs/common';
import { ExternalServiceService } from './external-service.service';
// @Injectable()
export class AppService {
@Inject(ExternalServiceService)
private externalServiceService: ExternalServiceService;
getHello(): string {
return this.externalServiceService.getHello();
}
}
// AppController.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
import { GLOBAL_APP_SERVICE } from './global';
@Controller()
export class AppController {
constructor(
@Inject(GLOBAL_APP_SERVICE) private readonly appService: AppService,
) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
// AppModule.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { GLOBAL_APP_SERVICE } from './global';
import { ExternalServiceService } from './external-service.service';
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: GLOBAL_APP_SERVICE,
useClass: AppService,
},
{
provide: 'GLOBAL_APP_KEY',
useValue: 'This is a global app key',
},
ExternalServiceService,
],
})
export class AppModule {}
不声明@Injectable 使用构造器注入
但若这个Provider需要使用到构造器注入时,则会遇到问题。因为缺少装饰器,TS无法生成对应的元数据
,Nest无法解析且注入对应的Provider实例。
import { Inject, Injectable } from '@nestjs/common';
import { ExternalServiceService } from './external-service.service';
// @Injectable()
export class AppService {
// @Inject(ExternalServiceService)
// private externalServiceService: ExternalServiceService;
constructor(
private readonly externalServiceService: ExternalServiceService,
) {}
getHello(): string {
return this.externalServiceService.getHello();
}
}