一、基础定义与核心作用
1.1 什么是@Injectable?
@Injectable()
是 NestJS 依赖注入(Dependency Injection, DI)系统的核心装饰器,用于将类标记为可注入的提供者(Provider)。它告知 NestJS 的 IoC(控制反转)容器:该类需要被实例化并管理其依赖关系。
1.2 核心功能
- 依赖注入(DI):通过构造函数自动注入依赖项,实现类之间的松耦合。
- 提供者注册:将类注册到 NestJS 的提供者容器中,使其可在其他组件(如控制器、服务)中被注入和使用。
- 生命周期管理:结合作用域(Scope)控制实例的创建和销毁时机。
1.3 典型示例
import { Injectable } from '@nestjs/common';@Injectable()
export class LoggerService {log(message: string) {console.log(`[Logger] ${message}`);}
}@Injectable()
export class UserService {constructor(private readonly logger: LoggerService) {}getUser() {this.logger.log('Fetching user...');return { id: 1, name: 'John' };}
}
二、高级特性详解
2.1 作用域(Scope)
NestJS 提供三种作用域,控制提供者实例的生命周期:
2.1.1 DEFAULT(单例)
- 行为:应用启动时创建一次实例,整个生命周期内复用。
- 适用场景:无状态服务(如日志服务、配置服务)。
- 配置:
@Injectable() // 默认即为单例 export class ConfigService {// 无状态配置逻辑 }
2.1.2 REQUEST
- 行为:每个 HTTP 请求创建新实例,实例仅在当前请求内有效。
- 适用场景:需维护请求上下文的服务(如用户会话、请求级缓存)。
- 配置:
import { Injectable, Scope } from '@nestjs/common';@Injectable({ scope: Scope.REQUEST }) export class UserSessionService {private userId: string;setUserId(id: string) {this.userId = id;}getUserId() {return this.userId;} }
2.1.3 TRANSIENT
- 行为:每次注入时创建新实例。
- 适用场景:需要新鲜实例的场景(如临时日志记录器)。
- 配置:
@Injectable({ scope: Scope.TRANSIENT }) export class TempLoggerService {log(message: string) {console.log(`[TempLog] ${message}`);} }
2.2 自定义提供者
通过模块的 providers
数组灵活配置依赖:
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { LoggerService } from './logger.service';@Module({providers: [UserService,{provide: 'CustomLogger',useClass: LoggerService, // 使用已有类},{provide: 'Config',useValue: { apiKey: '123' }, // 静态值},{provide: 'Database',useFactory: () => {// 工厂函数,可依赖其他提供者const config = new ConfigService();return new DatabaseConnection(config);},inject: [ConfigService], // 注入其他提供者},],exports: [UserService, 'CustomLogger'], // 导出供其他模块使用
})
export class AppModule {}
三、常见问题与解决方案
3.1 依赖未解析错误
错误:Nest can't resolve dependencies of the UserService (?)
原因:依赖未正确注册到模块的 providers
中。
解决方案:
@Module({providers: [UserService, LoggerService], // 确保所有依赖已注册controllers: [UserController],
})
export class UserModule {}
3.2 循环依赖
错误:两个服务相互依赖(A → B → A)。
解决方案:
- 重构代码:将共享逻辑提取到第三方服务。
- 使用
forwardRef
:@Module({imports: [forwardRef(() => OtherModule)], })
3.3 作用域冲突
问题:单例服务依赖请求作用域服务。
解决方案:
- 使用
@Inject(CONTEXT)
:import { INJECTABLE_METADATA } from '@nestjs/common';@Injectable() export class SingletonService {constructor(@Inject(CONTEXT) private context: any) {} }
四、与Angular的对比
4.1 相似性
- DI机制:均使用
@Injectable()
和@Inject()
装饰器。 - 模块化:通过模块(Module)组织依赖关系。
- 作用域:支持类似的作用域配置(如 Angular 的
providedIn
)。
4.2 差异
特性 | NestJS | Angular |
---|---|---|
运行环境 | Node.js 后端 | 浏览器前端 |
核心功能 | HTTP 服务器、微服务、GraphQL | SPA 开发、路由、模板引擎 |
作用域默认值 | DEFAULT (单例) | root (单例) |
典型场景 | API 开发、后端服务 | 客户端应用、组件化开发 |
五、最佳实践
5.1 模块化组织
- 按功能划分模块:将相关服务、控制器封装到独立模块。
- 导出公共服务:通过
exports
暴露公共提供者,避免全局污染。
5.2 作用域选择
- 优先单例:无状态服务使用默认单例作用域,优化性能。
- 请求作用域:需维护请求上下文时使用,注意实例创建开销。
5.3 测试策略
- 模拟依赖:使用
Test.createTestingModule()
模拟服务:describe('UserService', () => {let service: UserService;const mockLogger = { log: jest.fn() };beforeEach(async () => {const module: TestingModule = await Test.createTestingModule({providers: [UserService,{ provide: LoggerService, useValue: mockLogger },],}).compile();service = module.get<UserService>(UserService);}); });
六、总结
@Injectable()
是 NestJS 实现依赖注入和 IoC 的基石,通过合理使用其作用域、自定义提供者等功能,可构建出高可维护性、可扩展的后端应用。结合模块化设计和最佳实践,能进一步提升开发效率和代码质量。掌握 @Injectable()
的高级特性(如作用域、自定义提供者)是成为 NestJS 高级开发者的关键一步。