IOC控制反转与DI依赖注入
一、核心概念概述
1.1 什么是 IOC 和 DI
概念说明
- IOC(Inversion of Control,控制反转):一种设计模式/设计原则
- DI(Dependency Injection,依赖注入):IOC 的具体实现方式
核心理解
code
IOC 与 DI 的关系:
│
├── IOC(控制反转)
│ ├── 本质:设计模式 / 设计原则
│ ├── 目的:降低代码耦合度
│ └── 思想:借助第三方实现依赖解耦
│
├── DI(依赖注入)
│ ├── 本质:IOC 的具体实现
│ ├── 方式:在类外创建依赖对象
│ └── 优势:灵活、可测试、可维护
│
└── 一句话总结
└── IOC 是思想,DI 是实现1.2 为什么需要 IOC
传统开发的问题
code
传统开发模式的痛点:
│
├── 强耦合
│ ├── 类与类之间紧密依赖
│ ├── 修改一个类影响多个类
│ └── 难以替换和扩展
│
├── 难以测试
│ ├── 依赖对象在类内部创建
│ ├── 无法模拟依赖对象
│ └── 单元测试困难
│
├── 难以维护
│ ├── 代码修改影响范围大
│ ├── 需要修改多处代码
│ └── 违反开闭原则
│
└── 解决方案
└── IOC + DI 实现解耦二、从代码示例理解 IOC 与 DI
2.1 问题场景:强耦合的代码
需求描述
- 学生(Student)需要使用手机(Phone)玩游戏
- 最初实现:学生类直接依赖具体的手机类(iPhone)
强耦合实现(问题代码)
typescript
// ========== 问题代码:强耦合 ==========
/**
* iPhone 类
*/
class iPhone {
/**
* 打游戏
* @param name 学生姓名
*/
playGame(name: string): void {
console.log(`${name} 正在使用 iPhone 打游戏`);
}
}
/**
* 学生类
* 问题:与 iPhone 强耦合
*/
class Student {
private name: string;
constructor(name: string) {
this.name = name;
}
/**
* 玩游戏
* 问题:直接在类内部创建 iPhone 对象
*/
play(): void {
// 强耦合:直接依赖具体的 iPhone 类
const phone = new iPhone();
phone.playGame(this.name);
}
getName(): string {
return this.name;
}
setName(name: string): void {
this.name = name;
}
}
// ========== 使用示例 ==========
const student = new Student('张三');
student.play();
// 输出:张三 正在使用 iPhone 打游戏
// 问题:
// 1. 如果学生想用 Android 手机怎么办?
// 2. 必须修改 Student 类的代码
// 3. 违反开闭原则(对修改开放)
// 4. 难以进行单元测试问题分析
code
强耦合的问题:
│
├── 依赖具体实现
│ └── Student 依赖 iPhone,而不是抽象的 Phone
│
├── 难以扩展
│ └── 想要使用 Android 必须修改 Student 类
│
├── 难以测试
│ └── 无法模拟手机对象进行单元测试
│
└── 违反设计原则
├── 违反依赖倒置原则(DIP)
├── 违反开闭原则(OCP)
└── 违反单一职责原则(SRP)2.2 解决方案:依赖注入
第一步:定义抽象接口
typescript
// ========== 定义手机接口(抽象层)==========
/**
* 手机接口
* 抽象层:定义手机的通用行为
*/
interface Phone {
/**
* 打游戏
* @param name 学生姓名
*/
playGame(name: string): void;
}
/**
* iPhone 实现 Phone 接口
*/
class iPhone implements Phone {
playGame(name: string): void {
console.log(`${name} 正在使用 iPhone 打游戏`);
}
// iPhone 特有方法
callSiri(): void {
console.log('Hey Siri!');
}
}
/**
* Android 实现 Phone 接口
*/
class Android implements Phone {
playGame(name: string): void {
console.log(`${name} 正在使用 Android 打游戏`);
}
// Android 特有方法
useGoogleAssistant(): void {
console.log('OK Google!');
}
}
/**
* 华为手机实现 Phone 接口
*/
class Huawei implements Phone {
playGame(name: string): void {
console.log(`${name} 正在使用华为手机打游戏`);
}
// 华为特有方法
useXiaoyi(): void {
console.log('小艺小艺!');
}
}第二步:实现依赖注入
typescript
// ========== 使用依赖注入的学生类 ==========
/**
* 学生类(解耦版本)
* 通过依赖注入,不再依赖具体的手机类
*/
class DIStudent {
private name: string;
private phone: Phone; // 依赖抽象接口,而非具体实现
/**
* 构造函数注入
* @param name 学生姓名
* @param phone 手机对象(从外部注入)
*/
constructor(name: string, phone: Phone) {
this.name = name;
this.phone = phone; // 依赖从外部注入,而非内部创建
}
/**
* 玩游戏
* 不再关心具体是什么手机
*/
play(): void {
// 调用接口方法,具体实现由注入的对象决定
this.phone.playGame(this.name);
}
getName(): string {
return this.name;
}
setName(name: string): void {
this.name = name;
}
// 可以动态更换手机
setPhone(phone: Phone): void {
this.phone = phone;
}
}
// ========== 使用示例 ==========
// 场景 1:学生使用 Android 手机
const phone1 = new Android();
const student1 = new DIStudent('张三', phone1);
student1.play();
// 输出:张三 正在使用 Android 打游戏
// 场景 2:学生使用 iPhone
const phone2 = new iPhone();
const student2 = new DIStudent('李四', phone2);
student2.play();
// 输出:李四 正在使用 iPhone 打游戏
// 场景 3:学生使用华为手机
const phone3 = new Huawei();
const student3 = new DIStudent('王五', phone3);
student3.play();
// 输出:王五 正在使用华为手机打游戏
// 场景 4:动态更换手机
student1.setPhone(new iPhone());
student1.play();
// 输出:张三 正在使用 iPhone 打游戏2.3 核心变化对比
| 维度 | 强耦合版本 | 依赖注入版本 |
|---|---|---|
| 依赖对象创建 | 类内部 new iPhone() | 类外部创建,构造函数注入 |
| 依赖关系 | 依赖具体类 iPhone | 依赖抽象接口 Phone |
| 耦合度 | 高耦合 | 低耦合 |
| 可扩展性 | 差(需修改类代码) | 好(只需创建新实现类) |
| 可测试性 | 差(无法模拟依赖) | 好(可注入模拟对象) |
| 设计原则 | 违反 DIP、OCP | 符合 DIP、OCP、SRP |
2.4 控制反转的本质
反转了什么?
code
控制反转的本质:
│
├── 反转前(强耦合)
│ ├── Student 类控制 iPhone 的创建
│ ├── Student 决定使用哪个手机
│ └── 控制权在 Student 类内部
│
├── 反转后(依赖注入)
│ ├── 外部容器控制手机的创建
│ ├── 外部决定注入哪个手机
│ └── 控制权转移到外部容器
│
└── 本质
└── 控制权从类内部转移到外部容器图解控制反转
code
传统方式:
Student ──────强依赖──────> iPhone
│ │
└── 内部创建 new iPhone() ─────┘
控制权在 Student
依赖注入方式:
┌─────────────┐
│ 外部容器 │
│ (Container)│
└──────┬──────┘
│ 控制权在外部
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌───────┐ ┌─────────┐ ┌─────────┐
│iPhone │ │ Android │ │ Huawei │
└───────┘ └─────────┘ └─────────┘
│ │ │
└────────────┼────────────┘
│ 注入
▼
┌─────────┐
│ Student │
└─────────┘三、DI 的三种注入方式
3.1 构造函数注入
typescript
/**
* 构造函数注入(最常用)
* 优点:依赖关系明确,对象创建时就必须提供所有依赖
*/
class Student {
private name: string;
private phone: Phone;
constructor(name: string, phone: Phone) {
this.name = name;
this.phone = phone;
}
play(): void {
this.phone.playGame(this.name);
}
}
// 使用
const student = new Student('张三', new iPhone());3.2 属性注入
typescript
/**
* 属性注入
* 优点:灵活,可选依赖
* 缺点:依赖关系不明确,可能为 null
*/
class Student {
private name: string;
public phone!: Phone; // 可选依赖
constructor(name: string) {
this.name = name;
}
play(): void {
if (this.phone) {
this.phone.playGame(this.name);
} else {
console.log('没有手机可玩');
}
}
}
// 使用
const student = new Student('张三');
student.phone = new iPhone(); // 后续注入
student.play();3.3 方法注入
typescript
/**
* 方法注入
* 优点:最灵活,可以动态更换依赖
* 缺点:依赖关系最不明确
*/
class Student {
private name: string;
constructor(name: string) {
this.name = name;
}
play(phone: Phone): void {
phone.playGame(this.name);
}
}
// 使用
const student = new Student('张三');
student.play(new iPhone()); // 用 iPhone 玩
student.play(new Android()); // 用 Android 玩3.4 三种方式对比
| 注入方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 构造函数注入 | 依赖明确、不可变、易测试 | 参数多时构造函数复杂 | 必需依赖 |
| 属性注入 | 灵活、可选 | 依赖不明确、可能为 null | 可选依赖 |
| 方法注入 | 最灵活、动态更换 | 依赖最不明确 | 临时依赖 |
四、NestJS 中的依赖注入
4.1 NestJS DI 核心概念
三大核心概念
code
NestJS 依赖注入三大核心:
│
├── Provider(提供者)
│ ├── 被 @Injectable() 装饰的类
│ ├── 可以被注入到其他类中
│ └── Service、Repository、Helper 等
│
├── Token(令牌)
│ ├── 用于标识 Provider
│ ├── 通常是类名或字符串
│ └── 用于在容器中查找 Provider
│
└── Container(容器)
├── 管理所有 Provider 的创建和生命周期
├── 自动解析依赖关系
└── 自动注入依赖4.2 基本使用示例
完整示例:用户管理模块
typescript
// ========== 1. 定义接口(可选)==========
// src/users/interfaces/user.interface.ts
export interface User {
id: number;
name: string;
email: string;
}
// ========== 2. 创建 Service(Provider)==========
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { User } from './interfaces/user.interface';
@Injectable() // 标记为可注入的 Provider
export class UsersService {
private users: User[] = [
{ id: 1, name: '张三', email: 'zhangsan@example.com' },
{ id: 2, name: '李四', email: 'lisi@example.com' },
];
findAll(): User[] {
return this.users;
}
findOne(id: number): User | undefined {
return this.users.find(user => user.id === id);
}
create(user: User): User {
const newUser = { ...user, id: Date.now() };
this.users.push(newUser);
return newUser;
}
}
// ========== 3. 创建 Controller(注入 Service)==========
// src/users/users.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from './interfaces/user.interface';
@Controller('users')
export class UsersController {
// 构造函数注入:通过依赖注入获取 UsersService 实例
constructor(private readonly usersService: UsersService) {}
@Get()
findAll(): User[] {
// 使用注入的 Service
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string): User | undefined {
return this.usersService.findOne(+id);
}
@Post()
create(@Body() user: User): User {
return this.usersService.create(user);
}
}
// ========== 4. 创建 Module(注册 Provider)==========
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController], // 注册控制器
providers: [UsersService], // 注册提供者(Service)
exports: [UsersService], // 导出 Service(供其他模块使用)
})
export class UsersModule {}
// ========== 5. 在根模块中导入 ==========
// src/app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
@Module({
imports: [UsersModule],
})
export class AppModule {}4.3 工作原理详解
NestJS DI 工作流程
code
NestJS 依赖注入工作流程:
│
├── 1⃣ 启动阶段
│ ├── 扫描所有 Module
│ ├── 注册所有 Provider 到容器
│ └── 分析依赖关系图
│
├── 2⃣ 实例化阶段
│ ├── 根据依赖关系图
│ ├── 按拓扑排序创建实例
│ └── 自动注入依赖
│
├── 3⃣ 运行阶段
│ ├── 接收 HTTP 请求
│ ├── 路由到对应 Controller
│ └── Controller 调用注入的 Service
│
└── 4⃣ 响应阶段
└── 返回处理结果依赖关系图示例
code
依赖关系图:
AppModule
│
├── UsersModule
│ │
│ ├── UsersController
│ │ └── depends on: UsersService
│ │
│ ├── UsersService
│ │ └── depends on: (none)
│ │
│ └── exports: UsersService
│
└── AuthModule
│
├── AuthController
│ └── depends on: AuthService
│
├── AuthService
│ └── depends on: UsersService (imported from UsersModule)
│
└── imports: UsersModule4.4 高级用法:自定义 Provider
场景:注入第三方库或配置
typescript
// ========== 方式一:使用 useValue ==========
// src/database/database.module.ts
import { Module } from '@nestjs/common';
const databaseConfig = {
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'test',
};
@Module({
providers: [
{
provide: 'DATABASE_CONFIG', // 自定义 Token
useValue: databaseConfig, // 直接提供值
},
],
exports: ['DATABASE_CONFIG'],
})
export class DatabaseModule {}
// 使用
import { Inject, Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
constructor(
@Inject('DATABASE_CONFIG') private readonly dbConfig: any,
) {
console.log('数据库配置:', dbConfig);
}
}
// ========== 方式二:使用 useFactory ==========
// src/database/database.module.ts
import { Module } from '@nestjs/common';
@Module({
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: (config: any) => {
// 工厂函数:动态创建连接
return createConnection(config);
},
inject: ['DATABASE_CONFIG'], // 注入依赖
},
],
exports: ['DATABASE_CONNECTION'],
})
export class DatabaseModule {}
// ========== 方式三:使用 useClass ==========
@Module({
providers: [
{
provide: 'PhoneInterface',
useClass: iPhone, // 可以在运行时替换为 Android
},
],
})
export class PhoneModule {}
// 使用
@Injectable()
export class Student {
constructor(
@Inject('PhoneInterface') private phone: Phone,
) {}
play(): void {
this.phone.playGame('张三');
}
}4.5 循环依赖问题
问题场景
typescript
// 循环依赖示例
// service-a.service.ts
@Injectable()
export class ServiceA {
constructor(private serviceB: ServiceB) {} // 依赖 ServiceB
}
// service-b.service.ts
@Injectable()
export class ServiceB {
constructor(private serviceA: ServiceA) {} // 依赖 ServiceA
}
// 问题:A 依赖 B,B 依赖 A,形成循环依赖解决方案:使用 forwardRef
typescript
// 解决循环依赖
// service-a.service.ts
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { ServiceB } from './service-b.service';
@Injectable()
export class ServiceA {
constructor(
@Inject(forwardRef(() => ServiceB))
private serviceB: ServiceB,
) {}
}
// service-b.service.ts
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { ServiceA } from './service-a.service';
@Injectable()
export class ServiceB {
constructor(
@Inject(forwardRef(() => ServiceA))
private serviceA: ServiceA,
) {}
}
// 或者在 Module 中使用
@Module({
providers: [
{
provide: ServiceA,
useClass: ServiceA,
},
{
provide: ServiceB,
useClass: ServiceB,
},
],
})
export class AppModule {}五、TypeScript 装饰器实现 DI 原理
5.1 reflect-metadata 简介
概念说明
reflect-metadata 是一个库,用于在运行时添加和读取元数据。NestJS 使用它来实现依赖注入。
安装
bash
npm install reflect-metadata基本使用
typescript
// 在项目入口文件第一行导入
import 'reflect-metadata';
// 定义元数据
@Reflect.metadata('class', 'UserService')
class UserService {
@Reflect.metadata('method', 'findAll')
findAll() {
return 'all users';
}
}
// 读取元数据
const classMetadata = Reflect.getMetadata('class', UserService);
console.log(classMetadata); // 'UserService'
const methodMetadata = Reflect.getMetadata('method', UserService.prototype, 'findAll');
console.log(methodMetadata); // 'findAll'5.2 手写简化版 DI 容器
完整实现
typescript
// ========== 简化版 DI 容器实现 ==========
import 'reflect-metadata';
// ========== 1. 定义装饰器 ==========
/**
* Injectable 装饰器
* 标记类为可注入的 Provider
*/
function Injectable() {
return function(target: any) {
// 在类上添加元数据,标记为可注入
Reflect.defineMetadata('injectable', true, target);
};
}
/**
* Inject 装饰器
* 用于属性注入
*/
function Inject(token?: string) {
return function(target: any, propertyKey: string) {
// 获取属性类型
const type = Reflect.getMetadata('design:type', target, propertyKey);
const injectionToken = token || type;
// 在属性上添加元数据
Reflect.defineMetadata('inject', injectionToken, target, propertyKey);
};
}
// ========== 2. 定义容器类 ==========
/**
* DI 容器
*/
class Container {
private providers = new Map<any, any>();
/**
* 注册 Provider
*/
register(token: any, provider: any) {
this.providers.set(token, provider);
}
/**
* 获取 Provider 实例
*/
get<T>(token: any): T {
// 检查是否已注册
if (!this.providers.has(token)) {
throw new Error(`Provider ${token} not found`);
}
// 如果是类,创建实例
const Provider = this.providers.get(token);
// 检查是否标记为 Injectable
if (!Reflect.getMetadata('injectable', Provider)) {
throw new Error(`${Provider.name} is not injectable`);
}
// 获取构造函数参数类型
const paramTypes = Reflect.getMetadata('design:paramtypes', Provider) || [];
// 递归解析依赖
const dependencies = paramTypes.map((paramType: any) => this.get(paramType));
// 创建实例
const instance = new Provider(...dependencies);
// 处理属性注入
for (const key of Object.keys(instance)) {
const injectionToken = Reflect.getMetadata('inject', instance, key);
if (injectionToken) {
instance[key] = this.get(injectionToken);
}
}
return instance;
}
}
// ========== 3. 使用示例 ==========
// 定义接口
interface Phone {
playGame(name: string): void;
}
// 实现
@Injectable()
class iPhone implements Phone {
playGame(name: string): void {
console.log(`${name} 正在使用 iPhone 打游戏`);
}
}
@Injectable()
class Android implements Phone {
playGame(name: string): void {
console.log(`${name} 正在使用 Android 打游戏`);
}
}
@Injectable()
class Student {
constructor(private phone: Phone) {}
play(name: string): void {
this.phone.playGame(name);
}
}
// ========== 4. 注册和获取实例 ==========
const container = new Container();
// 注册 Provider
container.register('Phone', iPhone);
container.register(Student, Student);
// 获取实例
const student = container.get<Student>(Student);
student.play('张三');
// 输出:张三 正在使用 iPhone 打游戏
// 替换实现
container.register('Phone', Android);
const student2 = container.get<Student>(Student);
student2.play('李四');
// 输出:李四 正在使用 Android 打游戏5.3 装饰器执行顺序
typescript
// 装饰器执行顺序示例
function ClassDecorator() {
console.log('1. 类装饰器');
return function(target: any) {
console.log('4. 类装饰器执行');
};
}
function PropertyDecorator() {
console.log('2. 属性装饰器');
return function(target: any, propertyKey: string) {
console.log('5. 属性装饰器执行');
};
}
function MethodDecorator() {
console.log('3. 方法装饰器');
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('6. 方法装饰器执行');
};
}
@ClassDecorator()
class Example {
@PropertyDecorator()
name: string = '';
@MethodDecorator()
method() {}
}
// 执行顺序:
// 1. 类装饰器(定义时)
// 2. 属性装饰器(定义时)
// 3. 方法装饰器(定义时)
// 4. 类装饰器执行(实例化时)
// 5. 属性装饰器执行(实例化时)
// 6. 方法装饰器执行(实例化时)六、DI 在测试中的应用
6.1 单元测试优势
为什么 DI 有利于测试?
typescript
// ========== 不使用 DI(难以测试)==========
class UserService {
private httpService = new HttpService(); // 直接创建依赖
async getUsers() {
return this.httpService.get('/users');
}
}
// 测试困难:无法模拟 HttpService
// ========== 使用 DI(易于测试)==========
class UserService {
constructor(private httpService: HttpService) {} // 注入依赖
async getUsers() {
return this.httpService.get('/users');
}
}
// 测试:可以注入模拟对象
const mockHttpService = {
get: jest.fn().mockResolvedValue([{ id: 1, name: '张三' }]),
};
const userService = new UserService(mockHttpService as any);6.2 完整测试示例
typescript
// ========== 被测试的 Service ==========
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { User } from './interfaces/user.interface';
@Injectable()
export class UsersService {
private users: User[] = [];
constructor(
private logger: LoggerService,
private validator: ValidatorService,
) {}
findAll(): User[] {
this.logger.log('获取所有用户');
return this.users;
}
create(user: User): User {
if (!this.validator.validate(user)) {
throw new Error('用户数据验证失败');
}
const newUser = { ...user, id: Date.now() };
this.users.push(newUser);
this.logger.log(`创建用户:${newUser.name}`);
return newUser;
}
}
// ========== 测试文件 ==========
// src/users/users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { LoggerService } from '../common/logger.service';
import { ValidatorService } from '../common/validator.service';
describe('UsersService', () => {
let service: UsersService;
let loggerService: LoggerService;
let validatorService: ValidatorService;
// 模拟依赖
const mockLoggerService = {
log: jest.fn(),
error: jest.fn(),
};
const mockValidatorService = {
validate: jest.fn().mockReturnValue(true),
};
beforeEach(async () => {
// 创建测试模块
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: LoggerService,
useValue: mockLoggerService, // 注入模拟对象
},
{
provide: ValidatorService,
useValue: mockValidatorService, // 注入模拟对象
},
],
}).compile();
service = module.get<UsersService>(UsersService);
loggerService = module.get<LoggerService>(LoggerService);
validatorService = module.get<ValidatorService>(ValidatorService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('应该返回用户列表', () => {
const result = service.findAll();
expect(result).toEqual([]);
expect(mockLoggerService.log).toHaveBeenCalledWith('获取所有用户');
});
});
describe('create', () => {
it('应该成功创建用户', () => {
const user = { name: '张三', email: 'zhangsan@example.com' };
const result = service.create(user as any);
expect(result.name).toBe('张三');
expect(mockValidatorService.validate).toHaveBeenCalled();
expect(mockLoggerService.log).toHaveBeenCalledWith('创建用户:张三');
});
it('验证失败时应该抛出错误', () => {
mockValidatorService.validate.mockReturnValue(false);
const user = { name: '', email: 'invalid' };
expect(() => service.create(user as any)).toThrow('用户数据验证失败');
});
});
});七、最佳实践
7.1 设计原则
code
DI 最佳实践原则:
│
├── 依赖倒置原则(DIP)
│ ├── 高层模块不应依赖低层模块
│ ├── 两者都应依赖抽象
│ └── 抽象不应依赖细节
│
├── 单一职责原则(SRP)
│ ├── 每个 Service 只负责一个业务领域
│ ├── 避免上帝类(God Class)
│ └── 合理拆分职责
│
├── 接口隔离原则(ISP)
│ ├── 使用小而专一的接口
│ ├── 避免"胖"接口
│ └── 客户端不应依赖它不需要的方法
│
└── 开闭原则(OCP)
├── 对扩展开放
├── 对修改关闭
└── 通过接口和 DI 实现扩展7.2 命名约定
typescript
// ========== 推荐命名约定 ==========
// Service: 业务逻辑
@Injectable()
export class UsersService {}
// Repository: 数据访问
@Injectable()
export class UsersRepository {}
// Controller: 路由处理
@Controller('users')
export class UsersController {}
// Guard: 权限守卫
@Injectable()
export class AuthGuard implements CanActivate {}
// Interceptor: 拦截器
@Injectable()
export class LoggingInterceptor implements NestInterceptor {}
// Pipe: 管道
@Injectable()
export class ValidationPipe implements PipeTransform {}
// Filter: 过滤器
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {}7.3 Module 组织最佳实践
typescript
// ========== 功能模块组织 ==========
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { UsersRepository } from './users.repository';
@Module({
controllers: [UsersController], // 控制器
providers: [UsersService, UsersRepository], // 提供者
exports: [UsersService], // 导出公共 API
})
export class UsersModule {}
// src/app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { AuthModule } from './auth/auth.module';
@Module({
imports: [
UsersModule,
AuthModule,
],
})
export class AppModule {}
// ========== 共享模块 ==========
// src/common/common.module.ts
import { Module, Global } from '@nestjs/common';
import { LoggerService } from './logger.service';
import { ConfigService } from './config.service';
@Global() // 全局模块
@Module({
providers: [LoggerService, ConfigService],
exports: [LoggerService, ConfigService], // 导出后所有模块可用
})
export class CommonModule {}八、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
Nest can't resolve dependencies | 未在 Module 中注册 Provider | 在 @Module 的 providers 中添加 Service |
| 循环依赖错误 | A 依赖 B,B 依赖 A | 使用 forwardRef 或重构代码 |
| Provider 重复注册 | 多个 Module 重复注册 | 使用共享模块或在根模块统一注册 |
| 注入 undefined | Provider 未正确导出 | 检查 Module 的 exports 配置 |
| 测试中依赖注入失败 | 未在测试模块中注册 Provider | 使用 Test.createTestingModule 创建测试模块 |
| 作用域问题 | 使用了错误的 Provider 作用域 | 理解 Singleton、Request、Transient 作用域 |
| 私有 Provider 无法注入 | 未在 exports 中导出 | 在 Module 的 exports 中添加 Provider |
| 装饰器顺序错误 | 装饰器执行顺序问题 | 理解装饰器执行顺序,正确组合使用 |
九、学习要点总结
核心要点
- IOC 本质:控制反转,将依赖对象的创建权从类内部转移到外部容器
- DI 本质:依赖注入,是 IOC 的具体实现方式
- 核心优势:降低耦合度、提高可测试性、提高可维护性
- NestJS 实现:通过
@Injectable()装饰器和 Module 系统实现 DI - 设计原则:依赖倒置原则(DIP)是 DI 的理论基础
行动建议
code
学习路径:
│
├── 第一阶段:理解概念(1-2 天)
│ ├── 理解 IOC 和 DI 的本质
│ ├── 对比强耦合和解耦的差异
│ └── 理解三种注入方式
│
├── 第二阶段:实践练习(1 周)
│ ├── 手写简化版 DI 容器
│ ├── 在 NestJS 中使用 DI
│ └── 编写单元测试
│
└── 第三阶段:深入应用(持续)
├── 理解 NestJS DI 内部实现
├── 解决循环依赖问题
└── 设计合理的模块架构十、延伸学习资源
官方资源
推荐阅读
- 《设计模式:可复用面向对象软件的基础》- 依赖倒置原则
- 《Clean Architecture》- 依赖注入与架构设计
- Martin Fowler - Inversion of Control Containers and the Dependency Injection pattern
练习建议
- 练习 1:手写一个简化版的 DI 容器
- 练习 2:将现有项目中的强耦合代码重构为依赖注入
- 练习 3:编写单元测试,使用模拟对象验证 DI 的优势
- 练习 4:解决一个循环依赖问题
- 练习 5:设计一个模块化架构,合理使用共享模块
延伸思考
code
思考题:
│
├── IOC 和工厂模式的区别是什么?
├── 为什么 NestJS 使用 Singleton 作为默认作用域?
├── 如何避免过度使用 DI?
├── DI 在微服务架构中的应用?
└── 如何设计一个高性能的 DI 容器?附录:DI 核心概念速查表
| 概念 | 说明 | NestJS 实现 |
|---|---|---|
| Provider | 可注入的服务 | @Injectable() |
| Token | 标识 Provider | 类名、字符串、Symbol |
| Container | 管理实例的容器 | NestJS 内部实现 |
| Injection | 注入方式 | 构造函数、属性、方法 |
| Module | 组织 Provider | @Module() |
| Scope | 实例作用域 | Singleton、Request、Transient |
| Circular Dependency | 循环依赖 | forwardRef |
笔记整理完成时间:2026-03-07
下一章节预告:NestJS 模块化架构深入