6月4日 15:43

NestJS提供者详解:四种注册方式、循环依赖和作用域选择

NestJS 的提供者(Provider)就是"可以被注入的东西"——@Injectable() 装饰的类,通过依赖注入(DI)容器管理生命周期,在控制器或其他服务里通过构造函数参数自动获得实例。服务是最常见的提供者,但提供者不只有服务:配置对象、数据库连接、工厂函数都可以是提供者。

最常用的提供者:服务(Service)

服务封装业务逻辑,控制器只负责接收请求和返回响应:

typescript
// users.service.ts import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { private users = []; create(name: string, email: string) { const user = { id: Date.now(), name, email }; this.users.push(user); return user; } findAll() { return this.users; } }
typescript
// users.controller.ts @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} // 自动注入 @Post() create(@Body() dto: CreateUserDto) { return this.usersService.create(dto.name, dto.email); } @Get() findAll() { return this.usersService.findAll(); } }

在模块里注册:

typescript
@Module({ controllers: [UsersController], providers: [UsersService], // 注册服务,DI 容器会自动创建实例 }) export class UsersModule {}

提供者的四种注册方式

useClass:默认方式

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

最常用,DI 容器自动 new 一个实例。

useValue:提供常量或外部对象

typescript
providers: [ { provide: 'API_KEY', useValue: process.env.API_KEY, // 直接给一个值 }, ]

注入时用 @Inject() 指定令牌:

typescript
constructor(@Inject('API_KEY') private apiKey: string) {}

适合配置值、环境变量、第三方 SDK 实例等不需要 DI 创建的东西。

useFactory:动态创建,可以注入依赖

typescript
providers: [ { provide: 'DATABASE_CONNECTION', useFactory: (configService: ConfigService) => { return createConnection({ host: configService.get('DB_HOST'), port: configService.get('DB_PORT'), }); }, inject: [ConfigService], // 声明 useFactory 需要哪些依赖 }, ]

useFactory 的参数由 inject 数组提供,DI 容器先解析 inject 里的依赖,再传给工厂函数。适合需要异步初始化、依赖其他服务的场景。

useExisting:别名

typescript
providers: [ UsersService, { provide: 'IUsersService', // 接口令牌 useExisting: UsersService, // 指向已有的提供者 }, ]

接口在 TypeScript 编译后不存在,不能用 provide: IUsersService,用字符串或 Symbol 令牌 + useExisting 是标准做法。

依赖注入令牌

DI 容器通过令牌(token)匹配依赖。令牌可以是类、字符串或 Symbol:

typescript
// 类令牌(最常见) constructor(private usersService: UsersService) {} // 字符串令牌 constructor(@Inject('API_KEY') private apiKey: string) {} // Symbol 令牌(避免命名冲突) export const DATABASE_CONNECTION = Symbol('DATABASE_CONNECTION'); constructor(@Inject(DATABASE_CONNECTION) private db: Connection) {}

类令牌最好用——类型安全,不需要 @Inject() 装饰器。字符串和 Symbol 令牌用在没有对应类的场景。

循环依赖

两个服务互相依赖会报错:Circular dependency detected

typescript
// service-a 依赖 service-b,service-b 又依赖 service-a @Injectable() export class ServiceA { constructor(private serviceB: ServiceB) {} // ❌ 循环依赖 }

解决方案:用 forwardRef 延迟解析:

typescript
@Injectable() export class ServiceA { constructor( @Inject(forwardRef(() => ServiceB)) private serviceB: ServiceB, ) {} } @Injectable() export class ServiceB { constructor( @Inject(forwardRef(() => ServiceA)) private serviceA: ServiceA, ) {} }

模块里也要加 forwardRef:

typescript
@Module({ imports: [forwardRef(() => ServiceBModule)], }) export class ServiceAModule {}

forwardRef 只是绕过了报错,说明设计有问题——更好的做法是提取公共逻辑到第三个服务,或者通过事件解耦。

提供者作用域

默认情况下所有提供者都是单例(Singleton)——整个应用共享一个实例。NestJS 支持三种作用域:

作用域生命周期适用场景
DEFAULT(单例)应用启动时创建,共享几乎所有服务
REQUEST每个请求创建一个实例请求上下文数据(如当前用户)
TRANSIENT每次注入都创建新实例无状态的临时对象
typescript
@Injectable({ scope: Scope.REQUEST }) export class RequestContextService { private userId: string; setUserId(id: string) { this.userId = id; } getUserId() { return this.userId; } }

注意:REQUEST 作用域的服务,注入它的控制器也必须是 REQUEST 作用域。而且 REQUEST 作用域会显著影响性能——每个请求都创建新实例,数据库连接等资源不能共享。大多数场景用 DEFAULT + 在请求对象上挂数据就够了。

模块间共享提供者

默认情况下,模块的提供者对外不可见。要让其他模块用你的服务,必须 export:

typescript
// users.module.ts @Module({ controllers: [UsersController], providers: [UsersService], exports: [UsersService], // 暴露给其他模块 }) export class UsersModule {}

其他模块 import 后就能注入 UsersService:

typescript
// posts.module.ts @Module({ imports: [UsersModule], // import 整个模块 providers: [PostsService], }) export class PostsModule {} // posts.service.ts @Injectable() export class PostsService { constructor(private usersService: UsersService) {} // 可以用了 }

关键规则:import 模块,注入 export 的服务。不能直接 import 服务,也不能注入没 export 的服务。

可选注入

某些依赖不是必须的,找不到时不报错:

typescript
import { Optional } from '@nestjs/common'; @Injectable() export class MyService { constructor(@Optional() private cacheService?: CacheService) {} getData() { return this.cacheService?.get('key') ?? this.fetchFromDB(); } }

有 CacheService 就用缓存,没有就直接查数据库。适合功能增强型依赖。

标签:NestJS