4. NestJS 依赖注入(DI)深入:Provider、Module、自定义Provider、工厂模式、异步Provider

依赖注入是 NestJS 架构的基石。在微服务场景下,服务间的解耦、熔断器的动态切换、配置的异步加载,都依赖对 DI 系统的深度理解。本章将穿透 Provider 与 Module 的核心机制,掌握从标准注入到工厂模式、异步 Provider 的进阶用法。

4.1 Provider 的本质与分类

在 NestJS 中,几乎所有的东西都可以作为 Provider:服务、仓库、工厂、配置对象、甚至是值。Provider 的核心职责是“提供实例”,而 DI 容器负责将这些实例注入到需要它们的构造函数中。

Provider 类型 语法糖 适用场景
标准类 Provider @Injectable() + 类名 绝大多数业务服务
值 Provider { provide: TOKEN, useValue: ... } 常量、配置对象、第三方库实例
类 Provider { provide: TOKEN, useClass: ... } 策略模式、环境切换、Mock 测试
工厂 Provider { provide: TOKEN, useFactory: ... } 动态创建、依赖外部条件、异步初始化
别名 Provider { provide: TOKEN, useExisting: ... } 为已有 Provider 创建别名
关键认知:Provider 的 Token 可以是字符串、Symbol 或类本身。当使用类作为 Token 时,Nest 会自动推断类型;使用字符串或 Symbol 时,必须配合 @Inject() 装饰器显式注入。

4.2 Module 的封装与导出

Module 是组织 Provider 的逻辑单元。每个模块可以声明自己的 Provider,并通过 exports 数组决定哪些 Provider 对外可见。微服务架构中,合理的模块划分决定了系统的可维护性。

// 基础模块结构
@Module({
  imports: [ConfigModule],          // 导入其他模块
  controllers: [OrderController],   // 控制器
  providers: [OrderService],        // 注册 Provider
  exports: [OrderService],          // 导出供其他模块使用
})
export class OrderModule {}
模块作用域规则:
  • Provider 默认只在声明它的模块内可见。
  • 必须通过 exports 显式导出,其他模块才能注入。
  • 全局模块使用 @Global() 装饰器,但应谨慎使用,避免隐式依赖。
  • 动态模块(forRoot/forFeature)允许在导入时传递配置参数。

4.3 自定义 Provider:从 Token 到实例的完全控制

当标准类注入无法满足需求时,自定义 Provider 提供了对实例化过程的完全控制。这在熔断降级场景中尤为重要——你可以根据运行时状态动态切换服务实现。

4.3.1 值 Provider:注入常量与配置

// 定义 Token
export const CONFIG_OPTIONS = 'CONFIG_OPTIONS';

// 模块中注册
@Module({
  providers: [
    {
      provide: CONFIG_OPTIONS,
      useValue: {
        circuitBreakerTimeout: 5000,
        maxRetries: 3,
      },
    },
  ],
})
export class AppModule {}

// 注入使用
@Injectable()
export class CircuitBreakerService {
  constructor(@Inject(CONFIG_OPTIONS) private options: any) {}
}

4.3.2 类 Provider:运行时切换实现

在微服务中,你可能需要根据环境或配置切换熔断器实现。类 Provider 让这种切换变得优雅。

// 定义抽象接口
export interface ICircuitBreaker {
  call(fn: () => Promise<any>): Promise<any>;
}

// 两种实现
export class SimpleCircuitBreaker implements ICircuitBreaker { /* ... */ }
export class AdvancedCircuitBreaker implements ICircuitBreaker { /* ... */ }

// 根据环境变量动态选择
@Module({
  providers: [
    {
      provide: 'CIRCUIT_BREAKER',
      useClass: process.env.NODE_ENV === 'production'
        ? AdvancedCircuitBreaker
        : SimpleCircuitBreaker,
    },
  ],
})
export class AppModule {}

4.4 工厂 Provider:动态创建与依赖组合

工厂 Provider 是 DI 系统中最强大的工具之一。它允许你注入其他 Provider,并在运行时执行任意逻辑来创建实例。这对于需要异步初始化、条件创建或复杂依赖组合的场景至关重要。

// 工厂 Provider 示例:创建带配置的 Redis 客户端
@Module({
  providers: [
    {
      provide: 'REDIS_CLIENT',
      useFactory: (configService: ConfigService, logger: Logger) => {
        const options = configService.get('redis');
        logger.log(`Connecting to Redis at ${options.host}:${options.port}`);
        return new RedisClient(options);
      },
      inject: [ConfigService, Logger],  // 工厂函数的依赖
    },
  ],
})
export class RedisModule {}
工厂 Provider 注意事项:
  • inject 数组中的 Provider 必须已在当前模块或导入模块中注册。
  • 工厂函数可以是同步的,也可以是异步的(返回 Promise)。
  • 工厂函数中不要保留有状态副作用,保持幂等性。
Provider 类型与实例化流程 Provider 注册 useValue: 值/配置 useClass: 类实现 useFactory: 工厂函数 useExisting: 别名 NestJS DI 容器 Token → 实例 映射 单例 / 请求作用域 生命周期管理 循环依赖检测 异步初始化队列 注入目标 Controller Service Guard / Interceptor 其他 Provider 核心流程: 1. 模块解析 providers 数组 → 2. 根据类型(useValue/useClass/useFactory)创建实例 → 3. 注册到 DI 容器(Token → 实例) → 4. 构造函数注入时自动解析依赖

4.5 异步 Provider:微服务启动的关键

微服务启动时,经常需要等待数据库连接、配置中心拉取、服务发现注册等异步操作完成。异步 Provider 允许 DI 容器在 Provider 就绪后再进行依赖注入,避免服务在未初始化完成时被调用。

// 异步 Provider:等待数据库连接
@Module({
  providers: [
    {
      provide: 'DATABASE_CONNECTION',
      useFactory: async (configService: ConfigService) => {
        const connection = await createConnection(configService.get('db'));
        return connection;
      },
      inject: [ConfigService],
    },
  ],
})
export class DatabaseModule {}

// 使用异步 Provider 的服务
@Injectable()
export class UserService {
  constructor(
    @Inject('DATABASE_CONNECTION')
    private dbConnection: Connection,
  ) {}
}
异步 Provider 的微服务最佳实践:
  • 将数据库、消息队列、缓存等基础设施连接封装为异步 Provider。
  • 结合 onApplicationBootstrap 生命周期钩子,在应用完全启动后执行健康检查。
  • 异步 Provider 失败时,应抛出明确异常,让 NestJS 的启动过程失败(fail-fast)。
  • 考虑使用 @Optional() 装饰器处理可选依赖,避免启动阻塞。

4.6 自定义 Provider 在熔断降级中的实战模式

结合熔断降级场景,自定义 Provider 可以实现运行时策略切换、动态配置注入、以及服务降级时的优雅回退。

// 熔断器策略工厂:根据熔断状态动态选择实现
@Module({
  providers: [
    {
      provide: 'CIRCUIT_BREAKER_STRATEGY',
      useFactory: (stateStore: CircuitBreakerStateStore) => {
        // 根据当前熔断状态返回不同的策略实现
        const state = stateStore.getCurrentState();
        if (state === 'OPEN') {
          return new FallbackStrategy();       // 熔断打开,直接返回降级结果
        } else if (state === 'HALF_OPEN') {
          return new ProbeStrategy();          // 半开状态,发送探测请求
        } else {
          return new NormalStrategy();         // 正常状态,执行完整调用
        }
      },
      inject: [CircuitBreakerStateStore],
    },
  ],
})
export class CircuitBreakerModule {}
工厂 Provider:熔断状态驱动的策略切换 熔断状态存储 CLOSED / OPEN / HALF_OPEN useFactory 根据状态选择策略 返回策略实例 NormalStrategy(正常) ProbeStrategy(探测) FallbackStrategy(降级) 服务调用方 注入 CIRCUIT_BREAKER_STRATEGY 熔断降级中的工厂模式优势: • 策略切换对调用方完全透明,调用方只依赖抽象接口 • 工厂函数可以访问 DI 容器中的其他服务(如配置、日志、监控)

4.7 Provider 作用域:单例、请求与瞬态

在微服务中,Provider 的生命周期管理直接影响资源使用和状态安全。NestJS 支持三种作用域:

作用域 装饰器 实例生命周期 微服务适用场景
单例(Singleton) 默认 应用生命周期内唯一实例 无状态服务、配置、连接池
请求(Request) @Injectable({ scope: Scope.REQUEST }) 每个请求创建新实例 请求上下文、租户隔离
瞬态(Transient) @Injectable({ scope: Scope.TRANSIENT }) 每次注入创建新实例 状态敏感的辅助类、策略对象
作用域使用建议:在微服务中,优先使用单例作用域以节省资源。请求作用域会为每个请求创建新实例,在高并发下可能带来性能开销。瞬态作用域应仅用于确实需要独立状态的场景。

4.8 本章小结

  • Provider 是 DI 的核心抽象,支持值、类、工厂、别名四种注册方式。
  • Module 通过 exports 控制 Provider 可见性,动态模块提供灵活的配置导入。
  • 工厂 Provider 是实现熔断降级策略切换的关键,允许根据运行时状态动态选择实现。
  • 异步 Provider 确保微服务基础设施就绪后再提供服务,避免启动时序问题。
  • 作用域管理决定了 Provider 实例的生命周期,需根据场景谨慎选择。