NestJS 依赖注入(DI)全解析:不仅仅是 new 一下
你对 NestJS 或 Angular 的依赖注入系统的直觉非常准:
"看起来不过就是自动 new 一下类而已。"
没错,从表面上看确实如此,但 NestJS DI 的底层机制和设计哲学远比这复杂,它不仅仅是自动创建对象,而是一套完整的 对象生命周期管理、依赖解耦和模块化架构系统。
下面我从 表象 → 原理 → 设计模式 → 实际威力 四个层次来讲透。
🧩 一、表象层:@Injectable() 只是标记可注入对象
ts
@Injectable()
export class UserService {
constructor(private db: DatabaseService) {}
}
看起来,Nest 启动时只是做了几件事:
- 检查
UserService是否带有@Injectable(); - 自动创建它的实例;
- 解析构造函数参数;
- 把依赖的
DatabaseService注入进去。
表面上看就是帮你 new UserService(new DatabaseService()),但它背后是一个自动、可扩展、可替换、可分层的对象创建系统。
⚙️ 二、原理层:NestJS DI 容器 = IoC + 依赖图解析器
Nest 的 DI 核心是 IoC 容器(Inversion of Control Container) 。
它本质上是 "对象工厂 + 依赖图解析器":
ts
// 传统写法:
const userService = new UserService(new DatabaseService());
// Nest DI:
const userService = container.resolve(UserService);
容器会自动:
- 扫描所有
@Injectable()类; - 构建依赖图,解析依赖关系;
- 递归实例化依赖对象;
- 管理对象生命周期(单例、请求作用域、瞬时);
- 支持替换依赖(不同环境或 mock)。
这就是"控制反转(IoC)"的真正含义:业务对象不负责创建依赖,由容器统一管理。
🧱 三、设计模式层:DI 是多种经典模式的集合
| 模式 | DI 中体现 |
|---|---|
| 工厂模式(Factory) | 容器负责对象创建 |
| 单例模式(Singleton) | 默认 provider 全局单例 |
| 策略模式(Strategy) | 不同 token 提供不同实现 |
| 装饰器模式(Decorator) | @Injectable()、@Module() 保存元信息 |
| 依赖反转原则(DIP) | 上层依赖抽象接口而非具体实现 |
💡 实例:切换实现而不改业务代码
ts
export abstract class Logger {
abstract log(message: string): void;
}
@Injectable()
export class ConsoleLogger implements Logger {
log(msg: string) { console.log(msg); }
}
@Injectable()
export class FileLogger implements Logger {
log(msg: string) { writeFileSync('log.txt', msg); }
}
// 模块中切换实现:
@Module({
providers: [{ provide: Logger, useClass: FileLogger }],
})
export class AppModule {}
业务层依赖 Logger 抽象类,最终实现由模块配置决定。
这就是 依赖反转 + 策略模式 的威力。
🧠 四、实际威力:可扩展、可测试、可替换、可插拔
1️⃣ 可扩展性
模块化系统通过 provider 注入,实现插件式扩展:
ts
@Module({
imports: [DatabaseModule, UserModule, AuthModule],
})
export class AppModule {}
模块之间解耦,可随时增删。
2️⃣ 可测试性
单元测试时轻松 mock 依赖,无需改业务代码:
ts
const moduleRef = await Test.createTestingModule({
providers: [
UserService,
{ provide: DatabaseService, useValue: mockDb },
],
}).compile();
3️⃣ 可插拔架构
开发支付模块:
ts
export abstract class PaymentProvider {
abstract pay(): Promise<void>;
}
不同 app 只需注册不同实现:
ts
{ provide: PaymentProvider, useClass: WechatPay }
{ provide: PaymentProvider, useClass: StripePay }
上层逻辑完全不变。
4️⃣ 生命周期控制
Nest 可控制实例作用域:
| Scope | 描述 |
|---|---|
| DEFAULT | 全局单例 |
| REQUEST | 每个请求新实例 |
| TRANSIENT | 每次注入都新实例 |
既高效(单例),又能隔离(请求作用域)。