Skip to content

NestJS 的五种 Provider 提供者

Provider是nestjs中的核心,它提供了一种实现不同任务的方式,例如:数据访问、辅助函数等等,这些方式会在开发中尽可能的去做到可复用和可测试

Providers 的主要类型

共有 5 种 providers

  • 值提供者
  • 类提供者
  • 工厂提供者
  • 异步工厂提供者
  • 别名提供者

1. 值提供者 useValue

值提供者是最直接的一种类型,它直接返回一个常量或者预定义的值。且更常见的用例是,将应用的配置注入到需要读取配置的服务中。

例如:

ts
// 在模块定义中我们创建一个值 Provider
@Module({
  providers: [
    {
      provide: 'DATABASE_CONFIG', // 字符串token
      useValue: {
        host: 'localhost',
        port: 3306,
        user: 'root',
        password: '123456',
        database: 'blog'
      }
    }
  ]
})

// 在服务中,我们可以将这个值注入进来:
@Injectable()
export class DatabaseService {
  // 方式1: 构造器
  constructor(@Inject('DATABASE_CONFIG') private dbConfig: any) {}

  // 方式2: 属性注入
  @Inject('DATABASE_CONFIG')
  private dbConfig: any


  getConnectionDetails() {
    // 输出数据库配置信息
    console.log(this.dbConfig);
  }
}

2. 类提供者 useClass

类提供者是 Provider 中常用的一种,它可以让我们通过依赖注入的方式来获取类的实例。这对复用和测试非常有价值,因为我们可以在测试时使用模拟的实例来替换真正的服务。

例如:

ts
// 我们首先在模块定义中声明一个类提供者

// 方式1: 简写
@Module({
  providers: [ConfigService],
})

// 方式2: 完整写法(class做tokan)
@Module({
  providers: [{
    provide: ConfigService, // class做tokan
    useClass: ConfigService
  }],
})

// 方式3: 完整写法(字符串做tokan)
@Module({
  providers: [{
    provide: 'config_service', // 字符串做tokan
    useClass: ConfigService
  }],
})

// 接着在服务或者controller中,我们可以注入这个提供者
@Injectable()
export class UserService {
  // 方式1: class做token的时候--构造器注入
  constructor(private configService: ConfigService) {}
  // 方式2: string做toke, 构造器注入
  constructor(@Inject('config_service') private configService: ConfigService) {}

  // 方式3: class做token,---- 属性注入
  @Inject(configService)
  private configService: ConfigService

  // 方式4: string做token, 属性注入
    @Inject('config_service') // 这里需要和providers里面的token一样
  private configService: ConfigService

  getEnvironment() {
    // 输出当前应用环境
    console.log(this.configService.get('ENV'))
  }
}

3. 工厂提供者 Provider (useFactory)

工厂提供者是最灵活的 Provider 形式。工厂函数可以返回任意值,并且可以用来执行复杂的同步或异步操作。同样的,这让我们可以根据运行时逻辑返回不同的结果或者根据不同的运行环境提供不同的实现。

例如:

ts
// 在模块定义中,我们创建一个工厂提供者
@Module({
  providers: [
    {
      provide: 'ENV_CONFIG',
      useFactory: () => {
        // 例如,我们可以在此基于一些环境变量返回不同的配置:
        return process.env.NODE_ENV === 'development'
          ? developmentConfig
          : productionConfig;
      },
    },
  ],
})

// 届时我们可以在服务中注入这个工厂提供者
@Injectable()
export class UserService {

  // 构造函数参数注入
  constructor(@Inject('ENV_CONFIG') private envConfig: any) {}

  // 或者
  @Inject('ENV_CONFIG')
  private envConfig: any

  getConfig() {
    // 输出当前的环境配置
    console.log(this.envConfig);
  }
}

4. 异步工厂提供者 useFactory

异步工厂提供者与工厂提供者在原则上是相似的,但它们返回一个 Promise 或 Observable。当 Promise 解析或者 Observable 发射出结果时,这个结果就会在应用中作为注入的值。这项特性对于需要进行异步操作来提供值的需求场景非常有用,比如数据库连接,远程配置等。

例如:

ts
//在模块定义中我们先定义一个异步工厂提供者
@Module({
  providers: [
    {
      provide: 'DATABASE_CONNECTION',
      useFactory: async () => {
        const options = await getDbOptions();
        return createConnection(options);
      },
    },
  ],
})

// 在服务中,我们可以注入这个数据库连接
@Injectable()
export class UserService {
  // 构造函数参数注入
  constructor(
    @Inject('DATABASE_CONNECTION') private dbConnection: Connection
  ) {}

  // 或者 属性注入
   @Inject('DATABASE_CONNECTION')
   private dbConnection: Connection


  findUser(id: number) {
    return this.dbConnection.getRepository(User).findOne(id);
  }
}

5. 别名提供者 useExisting

别名提供者允许我们为提供者赋予别名,这样我们可以在不同上下文中引用并使用同样的值。这在某些需要多次引用同一个提供者或者希望使用更具语义化名称的场景中很有用。

ts
// 在模块定义中,我们创建 ConfigService 的别名
@Module({
  providers: [
    ConfigService,
    {
      provide: 'AppConfig',
      useExisting: ConfigService,
    },
  ],
})

// 在服务中,我们可以使用别名来注入这个提供者
@Injectable()
export class UserService {
  // 构造函数参数注入
  constructor(@Inject('AppConfig') private configService: ConfigService) {}

  // 或者属性注入
  @Inject('AppConfig')
  private configService: ConfigService

  getEnvironment() {
    // 输出当前应用环境
    console.log(this.configService.get('ENV'));
  }
}

在这个示例中,AppConfig 是 ConfigService 的别名,所以当我们请求 AppConfig 的时候,我们实际上获取到的是 ConfigService 的实例。

6. useFactory 通过参数注入其他provider

ts
@Module({
  providers:[
    {
      provide: 'person3',
      useFactory(person: { name: string }, appService: AppService) {
        return {
          name: person.name,
          desc: appService.getHello()
        }
      },
      inject: ['person', AppService]
    }
  ]
})

上述的provider 通过inject 声明了两个provider, 一个字符串token person,一个class token AppService, 这样 person3 这个token 所在的provide useFactory 参数内就能取到person 和 AppService

示例

ts
// module
@Module({
  imports: [],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: 'person',
      useValue: {
        name: 'person',
        desc: '123',
      },
    },
    {
      provide: 'person1',
      useFactory: (person: { desc: string }, appService: AppService) => {
        return {
          name: person.desc,
          desc: appService.getHello(),
        };
      },
      inject: ['person', AppService],
    },
    {
      provide: 'person3',
      useExisting: 'person',
    },
  ],
})
export class AppModule {}

// controller
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  @Inject()
  private readonly appService: AppService;
  @Inject('person')
  private person: { name: string; desc: string };
  @Inject('person1')
  private person1: { name: string; desc: string };
  @Inject('person3')
  private person3: { name: string; desc: string };

  // constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    const res = this.appService.getHello(); // Hello World
    const personName = this.person3.name + this.person3.desc; // 123Hello World
    console.log(res);
    console.log(personName);
    console.log(this.person1);

    return res + '-----------' + personName;
  }
}

小结

  • class做token时候, 通过构造函数参数注入
js
private xxxx: classxxxx
  • class做token时候, 通过属性注入
js
@Inject(classxxxx)
private xxxx: classxxxx
  • string 做 token, 通过 构造函数参数注入
js
@Inject('string') xxxx: xxxxclass
  • string 做 token, 通过属性注入
js
@Inject('string')
private xxxx: xxxxclass