6月2日 01:29

NestJS 依赖注入是怎么工作的?Module、Provider 和注入机制详解

NestJS 的依赖注入(DI)是从 Angular 借鉴的核心机制。你不需要手动创建实例和传递依赖——在 Provider 里声明,在构造函数里接收,Nest 容器自动装配。Module 是组织 Provider 的边界,控制哪些可以对外暴露、哪些只在内部使用。

依赖注入基本原理

没有 DI 的写法:手动创建依赖,耦合度高。

typescript
// 没有 DI const repo = new UserRepository(); const service = new UserService(repo); const controller = new UserController(service);

有 DI 的写法:只声明需要什么,Nest 自动注入。

typescript
@Controller('users') export class UsersController { constructor(private usersService: UsersService) {} // 自动注入 }

Nest 的 IoC 容器在启动时扫描所有 Module,根据构造函数的参数类型自动创建和注入实例。private 关键字同时声明和赋值——TypeScript 的参数属性简写。

Module:组织代码的边界

每个 Module 是一个独立的 DI 容器。Module 里注册的 Provider 默认只在 Module 内部可用。

typescript
@Module({ controllers: [UsersController], providers: [UsersService], exports: [UsersService], // 对外暴露 }) export class UsersModule {}
  • providers:注册到本 Module 的服务,本 Module 内可注入
  • exports:声明哪些 Provider 可以被其他 Module 使用
  • imports:导入其他 Module 暴露的 Provider
typescript
@Module({ imports: [UsersModule], // 导入后可以注入 UsersService providers: [PostsService], }) export class PostsModule {}

如果不 exports: [UsersService],PostsModule 里注入 UsersService 会报错——Nest 找不到这个 Provider。

Provider 的三种注册方式

1. 类名注册(最常见)

typescript
providers: [UsersService] // 等价于 providers: [{ provide: UsersService, useClass: UsersService }]

2. 值注册(Mock 或配置对象)

typescript
providers: [{ provide: 'CONFIG', useValue: { dbHost: 'localhost', port: 5432 }, }]

3. 工厂注册(动态创建,依赖其他 Provider)

typescript
providers: [{ provide: 'DATABASE_CONNECTION', useFactory: (configService: ConfigService) => { return createConnection(configService.get('db')); }, inject: [ConfigService], // 工厂的依赖 }]

注入方式

构造函数注入(推荐)

typescript
constructor(private usersService: UsersService) {}

属性注入(可选依赖)

typescript
@Inject('CONFIG') config: ConfigType<typeof config>;

@Inject() 指定 token——当 Provider 不是用类名注册时(字符串 token、Symbol token),必须显式指定。

作用域

默认情况下所有 Provider 是 Singleton(单例)——整个应用共享一个实例。这是最高效的模式,也是 99% 场景的正确选择。

其他作用域:

作用域生命周期适用场景
DEFAULT(Singleton)应用启动时创建,共享几乎所有情况
REQUEST每个请求创建新实例需要请求上下文(如租户隔离)
TRANSIENT每次注入都创建新实例极少用
typescript
@Injectable({ scope: Scope.REQUEST }) export class RequestLogger {}

不要随意改作用域——REQUEST scope 会显著增加内存和 GC 压力,因为每个请求都要创建和销毁实例。

循环依赖

A 依赖 B,B 依赖 A——Nest 无法确定先创建谁。解决方式:用 forwardRef 延迟解析。

typescript
@Module({ imports: [forwardRef(() => ModuleB)], }) export class ModuleA {}
typescript
constructor( @Inject(forwardRef(() => ServiceB)) private serviceB: ServiceB, ) {}

但循环依赖通常意味着设计有问题——考虑抽取共享逻辑到第三个 Module 里。

标签:NestJS