NestJS:Providers

131 阅读4分钟

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的组成分为TokenValue,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();
  }
}