Posted on October 03, 2024

Nest 依赖注入机制

Nest 依赖注入机制
为什么需要依赖注入?

在传统编码模式中,对象需主动创建和管理依赖:

class UserController {
  private userService: UserService;
  constructor() {
    this.userService = new UserService(); // 硬编码依赖
  }
}

此方式存在三大痛点:

  • ​耦合严重​:修改依赖需改动业务代码
  • 难以维护​:无法替换依赖的模拟实现
  • 职责混乱​:对象需同时承担业务和依赖管理

依赖注入通过控制反转(IoC)​​ 将依赖管理权移交框架,实现高内聚低耦合。

两个核心概念
  • ​控制反转(IoC)​:程序控制权从开发者转移至框架
  • 依赖注入(DI)​:通过参数传递依赖(构造函数/属性注入)
依赖注入的关键要素
​@Injectable()​

标记类可被注入:

@Injectable()
export class UserService {
  getUser() { /* ... */ }
}
@Inject()​

显式指定注入令牌(Token),用于非类依赖:

constructor(@Inject('CACHE') private cacheService) {}
forwardRef()​

解决循环依赖问题(如 A → B → A)

底层实现机制

TypeScript编译时通过emitDecoratorMetadata生成类型信息,运行时由reflect-metadata库读取:

Reflect.getMetadata('design:paramtypes', UserController);
// 输出:[UserService]
IoC 容器工作流
  1. 依赖声明与元数据收集
  2. 容器扫描类的构造函数参数,自动为构造函数参数生成元数据
  3. 容器创建单例实例,按需注入到依赖类中
依赖注入的三种方式
  • 构造器注入(主要方案)
constructor(private service: Service)
  • ​字段(属性)注入
@Inject() private readonly service: Service
  • Setter(方法)注入
setService(@Inject() service: Service){{
    this.service = service;
 }}
自定义Provider
  • ​useFactory 异步初始化或复杂构建
{ provide: Logger, useClass: DevLogger }
  • useValue 注入常量或模拟对象
{ provide: 'API_KEY', useValue: '12345' }
  • useClass 根据条件切换实现类(如测试环境用 Mock 类)
{ provide: Logger, useClass: DevMock }
  • useExisting:允许将一个已注册的 Provider 的实例通过新 Token 暴露给其他模块或类,实现“别名注入”
循环依赖解决方案

循环依赖是指两个或多个模块、类或服务相互直接或间接依赖,形成闭环,导致系统无法正常初始化,这里提供以下几种解决方法:

  • 提取公共逻辑到新模块(如 CommonService),打破直接依赖
  • 前向引用-forwardRef​,通过延迟解析依赖,打破初始化顺序限制
@Module({ imports: [forwardRef(() => AuthModule)] })
...
@Module({ imports: [forwardRef(() => AuthModule)] })
  • ModuleRef 手动查找依赖
private userService: UserService;
constructor(private moduleRef: ModuleRef) {}
...
onModuleInit() {
   this.userService = this.moduleRef.get(UserService);
}

博客

关联内容