如何通过规范的DI方式实例化携带动态参数的构造函数?
针对动态参数服务的DI实现方案
这个场景非常适合使用ModuleRef实现,若服务拆分在独立子模块中,也可搭配懒加载(Lazy Loading)实现,完全符合DI规范,相比手动实例化的方式可维护性更强。
现有手动实例化方案的问题
- 手动new服务的方式会导致服务内部的其他DI依赖无法被Nest容器自动注入,比如后续
OutletService需要依赖HttpService、ConfigService等全局注册的服务时,你需要手动传递所有依赖,维护成本极高 - 不符合依赖倒置原则,服务之间的耦合度高,不利于单元测试
基于ModuleRef的标准实现方案
核心思路是将所有服务改为标准@Injectable()装饰的可注入类,动态参数通过自定义注入Token传递,调用ModuleRef.create()方法在运行时传入动态参数创建实例,容器会自动解析所有依赖。
第一步:改造服务为标准可注入类
import { Injectable, Inject } from '@nestjs/common'; import type { RequestDTO } from './dto/request.dto'; // 定义自定义注入Token,用于传递动态apiKey export const API_KEY_TOKEN = Symbol('API_KEY'); @Injectable() export class OutletService { // apiKey通过注入Token获取,不需要手动传入 constructor(@Inject(API_KEY_TOKEN) private apiKey: string) {} // 其他业务逻辑 } @Injectable() export class NewService { // OutletService由DI容器自动注入,不需要手动new constructor( @Inject(API_KEY_TOKEN) private apiKey: string, private outletService: OutletService ) {} async triggerBoarding(dto: RequestDTO) { // 业务逻辑实现 } }
第二步:控制器中通过ModuleRef创建实例
import { Controller, Post, Body, ModuleRef } from '@nestjs/common'; import { NewService, API_KEY_TOKEN } from './new.service'; @Controller() export class YourController { // 注入当前模块的ModuleRef实例 constructor(private moduleRef: ModuleRef) {} @Post() public async handleRequest( @Body() requestDto: RequestDTO ): Promise<void> { // 运行时传入动态apiKey,创建NewService实例,容器自动解析所有依赖 const newService = await this.moduleRef.create(NewService, { providers: [ { provide: API_KEY_TOKEN, useValue: API_KEYS.get(requestDto.address.country) } ] }); await newService.triggerBoarding(requestDto); } }
懒加载的适配场景
如果你的NewService和关联服务属于独立的功能模块,不需要在应用启动时就加载,可以先懒加载对应模块,再用子模块的ModuleRef创建实例,适合中大型应用拆分模块、降低启动耗时的场景。如果服务就在当前模块内,不需要懒加载,直接用当前注入的ModuleRef即可。
可选优化方案
如果你的场景中多个服务都需要用到当前请求的apiKey,也可以考虑使用*请求作用域(Request Scoped)*的提供者,但请求作用域会触发依赖链上的所有服务都按请求实例化,有一定性能开销,相比之下ModuleRef.create()的方式更轻量,适合仅少数服务需要动态参数的场景。
内容的提问来源于stack exchange,提问作者styler
相关产品推荐
相关产品推荐

