什么是依赖注入(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');
  });
});
相关推荐
MATLAB代码顾问1 小时前
5大智能算法优化标准测试函数对比(Python实现)
开发语言·python
wuminyu2 小时前
专家视角看Java字节码加载与存储指令机制
java·linux·c语言·jvm·c++
万粉变现经纪人2 小时前
如何解决 pip install llama-cpp-python 报错 未安装 CMake/Ninja 或 CPU 不支持 AVX 问题
开发语言·python·开源·aigc·pip·ai写作·llama
清风明月一壶酒3 小时前
OpenClaw自动处理Word文档全流程
开发语言·c#·word
其实防守也摸鱼3 小时前
CTF密码学综合教学指南--第五章
开发语言·网络·笔记·python·安全·网络安全·密码学
callJJ4 小时前
Spring Data Redis 两种编程模型详解:同步 vs 响应式
java·spring boot·redis·python·spring
小郑加油4 小时前
python学习Day12:pandas安装与实际运用
开发语言·python·学习
AC赳赳老秦4 小时前
投标合规提效:用 OpenClaw 实现标书 / 合同自动审核、关键词校验、格式优化,降低废标风险
开发语言·前端·python·eclipse·emacs·deepseek·openclaw
KuaCpp4 小时前
C++面向对象(速过复习版)
开发语言·c++
wbs_scy4 小时前
Linux线程同步与互斥(三):线程同步深度解析之POSIX 信号量与环形队列生产者消费者模型,从原理到源码彻底吃透
java·开发语言