什么是依赖注入(DI)&控制反转IoC

一、从紧耦合问题说起

假设我们有一个 UserService 需要发送邮件,通常会直接 new 一个 EmailService

javascript 复制代码
// ❌ 紧耦合示例
class EmailService {
  send(recipient: string, content: string) { /* ... */ }
}

class UserService {
  private emailService = new EmailService(); // 直接创建依赖

  register(email: string) {
    this.emailService.send(email, 'Welcome');
  }
}

问题

  • 难以替换 EmailService 的实现(如换成 SmsService
  • 测试 UserService 时必须真实调用 EmailService,无法使用模拟对象。

  • EmailService 的构造函数发生变化,所有使用它的地方都要修改。

二、控制反转(IoC)

控制反转 :将对象的创建依赖查找 的控制权从类内部转移到外部容器(或框架)。

简单说:别找我要依赖,我会告诉你我需要什么,由别人给我。

传统(正向)控制

类主动创建或查找依赖 → 类控制依赖。

类只声明自己需要什么依赖(比如通过构造函数参数),由外部(IoC 容器)在实例化时把依赖"注入"进去 → 控制权反转给了容器。

javascript 复制代码
// ✅ 符合 IoC 原则:UserService 不关心 EmailService 如何创建
class UserService {
  constructor(private emailService: EmailService) {} // 只声明需要
  // ...
}

此时谁负责创建 EmailService 并传给 UserService?------ IoC 容器(NestJS 运行时)

三、依赖注入(DI)

依赖注入 是实现 IoC 的一种具体技术。它指:将组件所需的依赖(通常是其他服务或对象)通过构造函数、属性或方法参数的形式注入到组件中,而不是由组件自己创建。

NestJS 主要使用 构造函数注入(官方推荐),也支持属性注入(较少用)。

常见注入方式
  • 构造函数注入:依赖在实例化时通过构造函数传入。

  • 属性注入 :通过 @Inject() 装饰器直接注入到类属性。

  • 方法注入:通过方法参数注入(NestJS 不常用)。

四、NestJS 中的依赖注入实现

1. 基础三要素
  • 提供者(Provider) :可被注入的类,通常用 @Injectable() 装饰。

  • 消费者(Consumer):需要依赖的类,在构造函数中声明参数类型。

  • 模块(Module) :组织提供者,NestJS 根据模块的 providers 数组注册可注入的类。

2. 简单示例
TypeScript 复制代码
// email.service.ts
import { Injectable } from '@nestjs/common';

@Injectable() // 标记为可注入的提供者
export class EmailService {
  send(message: string) {
    console.log(`Sending: ${message}`);
  }
}
TypeScript 复制代码
// user.service.ts
import { Injectable } from '@nestjs/common';
import { EmailService } from './email.service';

@Injectable()
export class UserService {
  // 构造函数参数中声明依赖,NestJS 会自动注入 EmailService 的实例
  constructor(private readonly emailService: EmailService) {}

  register() {
    this.emailService.send('Welcome');
  }
}
TypeScript 复制代码
// app.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { EmailService } from './email.service';

@Module({
  providers: [UserService, EmailService], // 注册所有提供者
})
export class AppModule {}

当 NestJS 启动时:

  • 扫描 AppModuleproviders,创建每个提供者的实例(默认单例)。

  • 创建 UserService 时,发现它的构造函数需要 EmailService,于是从容器中取出已创建的 EmailService 实例,注入进去。

3. 注入令牌(Injection Token)
TypeScript 复制代码
// 使用自定义令牌
import { Inject } from '@nestjs/common';

const EMAIL_SERVICE = 'EMAIL_SERVICE';

@Injectable()
class UserService {
  constructor(@Inject(EMAIL_SERVICE) private emailService) {}
}

然后在模块中通过 useClassuseValueuseFactory 等方式提供:

TypeScript 复制代码
@Module({
  providers: [
    {
      provide: EMAIL_SERVICE,
      useClass: EmailService,
    },
  ],
})
export class AppModule {}
4. 作用域(Scope)

NestJS 中的提供者默认是单例(整个应用共享同一个实例)。你也可以改为请求作用域或瞬态作用域:

TypeScript 复制代码
@Injectable({ scope: Scope.REQUEST }) // 每个请求新建实例
export class RequestScopedService {}

五、为什么要在 NestJS 中使用 DI/IoC?

测试示例(使用 Jest)
TypeScript 复制代码
describe('UserService', () => {
  let userService: UserService;
  let mockEmailService: Partial<EmailService>;

  beforeEach(async () => {
    mockEmailService = { send: jest.fn() };
    const module = await Test.createTestingModule({
      providers: [
        UserService,
        { provide: EmailService, useValue: mockEmailService }, // 注入 mock
      ],
    }).compile();

    userService = module.get(UserService);
  });

  it('should send email on register', () => {
    userService.register();
    expect(mockEmailService.send).toHaveBeenCalledWith('Welcome');
  });
});
相关推荐
咖啡八杯11 小时前
GoF设计模式——模板方法模式
java·后端·spring·设计模式
爱笑的源码基地12 小时前
一款全开源的信创云PACS影像云平台解决方案,支持多模态医学影像处理(CT/MR/DR/超声/病理等)
java·源码·国产化·pacs·云影像·区域pacs
我命由我1234512 小时前
Android 开发问题:ClickableSpan 的点击事件没有生效
java·java-ee·android studio·android jetpack·android-studio·android runtime
2zcode12 小时前
基于MATLAB图像处理的饮料瓶灌装液位检测系统设计与实现
开发语言·图像处理·matlab
万亿少女的梦16812 小时前
基于Python的高考志愿填报辅助系统设计与实现
java·spring boot·python·mysql·vue
微信开发api-视频号协议12 小时前
企业微信外部群开发自动化实践过程
java·前端·微信·自动化·企业微信·ipad
犀利豆13 小时前
#AI in Harness(一)
java·ai编程
极***涯13 小时前
深度拆解:多源BFS路径重构难题——跳出单层遍历误区,搞定高阶最短路径全量解
java·重构·宽度优先
必须得开心呀13 小时前
QT解决中文乱码问题
开发语言·qt
YuK.W14 小时前
Leetcode100: 70.爬楼梯、118.杨辉三角、198.打家劫舍
java·算法·leetcode