模块系统与依赖注入
@Module 组织控制器与 Provider;依赖注入由 IoC 容器管理。见 导读、基础。
一、模块(@Module)
模块是用 @Module() 装饰器声明的类,它将一组控制器、提供者等组织在一起。每个 Nest 应用至少有一个根模块(AppModule)。
1. 4 个核心属性
| 属性 | 类型 | 描述 |
|---|---|---|
imports | Array<ModuleType> | 导入其他模块中导出的提供者(如服务、存储库) |
controllers | Array<ControllerType> | 该模块中需要实例化的控制器类 |
providers | Array<ProviderType> | 该模块中可注入的服务、工厂等提供者 |
exports | Array<string | symbol | ProviderType> | 导出本模块的提供者,供其他模块使用(需在 imports 中导入本模块) |
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
@Module({
imports: [], // 可导入其他模块
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // 允许其他模块使用 UsersService
})
export class UsersModule {}
2. 全局模块(@Global)
使用 @Global() 装饰器的模块称为全局模块,其导出的提供者可在所有模块中直接注入,无需在 imports 中声明。
@Global()
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class CommonModule {}
// 任意模块中都可以直接注入 ConfigService
适用场景:通用基础设施服务(配置模块、日志模块、数据库连接池等),避免在每个模块中重复导入。
3. 模块重导出
模块可以重新导出其他模块的提供者,实现聚合导出。
@Module({
imports: [DatabaseModule, CacheModule],
exports: [DatabaseModule, CacheModule], // 将这两个模块的提供者一并导出
})
export class DataAccessModule {}
这样其他模块只需导入 DataAccessModule 即可获得 DatabaseModule 和 CacheModule 的所有导出。
4. 动态模块(DynamicModule)
动态模块允许在导入时传递配置参数,从而动态生成模块的定义。
export class ConfigModule {
static forRoot(options: ConfigOptions): DynamicModule {
return {
module: ConfigModule,
providers: [
{
provide: "CONFIG_OPTIONS",
useValue: options,
},
ConfigService,
],
exports: [ConfigService],
};
}
}
// 导入时
@Module({
imports: [ConfigModule.forRoot({ path: "./config.json" })],
})
export class AppModule {}
常见的动态模块方法命名有:forRoot、forFeature、register 等(如 TypeORM、Mongoose 模块)。
二、依赖注入(DI)
NestJS 使用 IoC 容器管理提供者的生命周期和依赖关系。
1. 构造函数注入(推荐) vs 属性注入
- 构造函数注入:通过构造函数的参数声明依赖,清晰且易于测试。
@Injectable()
export class UsersService {
constructor(private readonly userRepository: UserRepository) {}
}
- 属性注入:使用
@Inject()装饰器直接注入到属性,适用于可选依赖或基类场景。
@Injectable()
export class UsersService {
@Inject("CONNECTION")
private readonly connection: Connection;
}
2. 自定义提供者
Nest 提供多种方式定义提供者,超越简单的类注入。
| 类型 | 描述 | 示例 |
|---|---|---|
useClass | 指定替代类 | { provide: Logger, useClass: FileLogger } |
useValue | 注入常量/对象 | { provide: 'PORT', useValue: 3000 } |
useFactory | 工厂函数,可注入其他提供者 | { provide: 'DB', useFactory: (config) => new DB(config), inject: [ConfigService] } |
useExisting | 别名(用于重新映射提供者) | { provide: MyService, useExisting: BetterService } |
@Module({
providers: [
{ provide: "DB_NAME", useValue: "postgres" },
{
provide: "CONNECTION",
useFactory: (dbName: string) => createConnection(dbName),
inject: ["DB_NAME"],
},
{ provide: Logger, useClass: FileLogger },
{ provide: OriginalService, useExisting: EnhancedService }, // 别名
],
})
export class AppModule {}
3. 异步提供者
useFactory 可以返回 Promise,使提供者异步创建。其他依赖该提供者的服务会等待其解析完成。
{
provide: 'ASYNC_DB',
useFactory: async (config: ConfigService) => {
const connection = await createDatabaseConnection(config);
return connection;
},
inject: [ConfigService],
}
4. 循环依赖解决方案
当两个类互相依赖时,Nest 无法决定实例化顺序。解决方案:使用 forwardRef() 函数。
@Injectable()
export class ServiceA {
constructor(@Inject(forwardRef(() => ServiceB)) private serviceB: ServiceB) {}
}
@Module({
imports: [forwardRef(() => ModuleB)],
})
export class ModuleA {}
最佳实践:尽量避免循环依赖,若无法避免,使用 forwardRef 并确保只在模块级别使用(不要过于深入)。
三、作用域(Scope)
提供者的生命周期由作用域决定。
| 作用域 | 描述 | 生命周期 |
|---|---|---|
| SINGLETON(默认) | 整个应用只有一个实例,所有请求共享 | 应用启动时创建,应用关闭时销毁 |
| REQUEST | 每个 HTTP 请求创建一个新实例(请求处理完后销毁) | 针对每个请求单独创建 |
| TRANSIENT | 每次注入(每个消费者)创建一个全新实例 | 按需创建,不共享 |
使用 @Injectable({ scope: Scope.REQUEST }) 或 @Injectable({ scope: Scope.TRANSIENT }) 指定。
性能影响与适用场景
- SINGLETON:性能最好,无额外开销。适合大多数无状态服务(如数据库服务、工具类、仓储)。
- REQUEST:每个请求都会创建实例,会增加内存分配和 GC 压力。适合需要携带请求上下文的实例(如请求级缓存、用户会话数据)。
- TRANSIENT:每次注入创建新实例,开销最大。适合状态独立、不应共享的服务(如某个特定计算对象、临时数据处理器)。
注意:REQUEST 和 TRANSIENT 作用域的提供者不能注入到 SINGLETON 提供者中(因为 SINGLETON 实例不会在请求结束后释放,会导致内存泄漏或状态混乱),除非通过 @Inject(forwardRef(...)) 或使用 Request 作用域的代理模式(NestJS 内部有处理,但需谨慎)。通常,尽量保持默认 SINGLETON 作用域。
总结
| 模块特性 | 作用 |
|---|---|
imports / exports | 模块间依赖共享 |
@Global | 全局模块,避免重复导入 |
| 重导出 | 聚合导出多个模块 |
| 动态模块 | 带配置参数的模块导入 |
| DI 特性 | 描述 |
|---|---|
| 构造函数注入 | 推荐注入方式,便于测试 |
| 自定义提供者 | useValue、useClass、useFactory、useExisting |
| 异步提供者 | 工厂可返回 Promise,依赖会等待 |
forwardRef | 解决循环依赖 |
| 作用域 | 适用场景 |
|---|---|
| SINGLETON(默认) | 无状态服务 |
| REQUEST | 需要请求级状态 |
| TRANSIENT | 每次需全新实例 |
掌握这些概念,可以设计出清晰、可测试、高性能的 NestJS 应用。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| NestJS 文档 | 官方 |
| Modules | 本章主题 |
| Request lifecycle | 执行顺序 |
相关文章
认证与授权
认证(Authentication):确认「你是谁」(如 JWT、Session)。 - 授权(Authorization):确认「你能做什么」(如 RBAC、策略检查)。
缓存
@nestjs/cache-manager 统一缓存 API,存储实现可插拔。
提供者与服务
Service 是最常见的 Provider,封装业务逻辑。见 module。
守卫 (Guards) 与授权
守卫决定是否放行请求,常用于认证与授权。见 auth。
配置管理(Config 模块)
@nestjs/config 加载 .env 并提供 ConfigService。见 工程化 env。
数据库集成(以 TypeORM 为例)
Nest 通过 @nestjs/typeorm 等包集成 ORM;生产环境用 migration,慎用 synchronize。亦可选用 Prisma、MikroORM 等(见 官方 Database)。
Series
new
15 / 19