你好,我是南一
什么是控制反转?
控制反转 是面向对象编程的一种设计思想,用于降低模块间的耦合度,以 NestJS 为例,只需在UserController构造函数中声明依赖userService的类型,框架层会创建userService依赖实例注入UserController中,依赖的创建和管理都由 NestJS 容器完成。
javascript
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
}
这样做有什么好处?
传统方式:既要声明,又要自己 new,在创建实例处产生强耦合。
javascript
// 原来
export class UserService {
constructor() {}
}
export class UserController {
private userService: UserService;
constructor() {
// 这里产生了强耦合:UserController 硬编码了 UserService 的实例化逻辑
this.userService = new UserService();
}
}
假设有10个地方使用了UserService,就会有10个地方进行UserService实例化;此时UserService增加一个依赖参数,那就需要修改10个地方,把 db 参数传入
javascript
// 后来增加了依赖
export class UserService {
constructor(private readonly db: DatabaseService) {}
}
此时,控制反转的好处就体现出来了,没有实例化逻辑,只需要在UserService上声明好DatabaseService,依赖会自动注入,其他10个使用到UserService的地方都不需要改。
底层原理解析
NestJS采用依赖注入的方式实现控制反转思想,通过 Reflect.metadata 获取构造函数的参数类型,在 DI 容器中查找对应类型的实例,自动调用构造函数并传入该实例。
依赖注入:其实就是把依赖在类外部实例化之后通过参数传入。使用 TypeScript 可以进一步简化赋值操作,只需给构造函数参数添加访问修饰符 private ,就能声明和赋值一个私有变量
typescript
class B {
constructor() {}
}
// class A {
// constructor(b) {
// this.b = b; // JS 传统写法
// }
// }
class A {
constructor(private b: B) {} // TS 写法
}
const b = new B(); // 在外部实例化 B
const a = new A(b); // 依赖注入
下面我用一个极简的 Demo 来演示 NestJS 控制反转(IoC)和依赖注入(DI)的底层实现原理。这个 Demo 会模拟 NestJS 的核心机制:装饰器收集元数据 + 容器扫描注册 + 自动实例化注入。
1. 核心工具:Reflect.metadata 模拟
NestJS 底层依赖 reflect-metadata 库来存储类的元数据。我们先实现一个简易版本:
typescript
// 模拟 Reflect.metadata 的简易实现
const metadataMap = new Map<string, any>();
function defineMetadata(target: any, key: string, value: any) {
const targetKey = target.name || target.constructor.name;
if (!metadataMap.has(targetKey)) {
metadataMap.set(targetKey, {});
}
metadataMap.get(targetKey)[key] = value;
}
function getMetadata(target: any, key: string) {
const targetKey = target.name || target.constructor.name;
return metadataMap.get(targetKey)?.[key];
}
2. 实现 @Injectable 装饰器
@Injectable() 装饰器的作用是标记一个类可以被容器管理:
typescript
function Injectable() {
return function(target: any) {
// 标记该类为可注入的
defineMetadata(target, 'injectable', true);
// 获取构造函数参数的类型信息(实际 NestJS 通过 TypeScript 编译时生成)
// 这里我们手动模拟
const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
defineMetadata(target, 'paramTypes', paramTypes);
};
}
3. 实现简易 DI 容器
这是整个机制的核心------IoC 容器:
typescript
class Container {
private instances = new Map<string, any>();
private providers = new Map<string, any>();
// 注册提供者
addProvider(token: string, provider: any) {
this.providers.set(token, provider);
}
// 获取实例(自动解析依赖)
get<T>(token: string): T {
// 如果已有实例,直接返回(单例模式)
if (this.instances.has(token)) {
return this.instances.get(token);
}
const provider = this.providers.get(token);
if (!provider) {
throw new Error(`Provider ${token} not found`);
}
// 获取构造函数的参数类型
const paramTypes = getMetadata(provider, 'paramTypes') || [];
// 递归解析所有依赖
const dependencies = paramTypes.map((dep: any) => {
const depName = dep.name;
return this.get(depName);
});
// 创建实例并注入依赖
const instance = new provider(...dependencies);
// 缓存实例(单例)
this.instances.set(token, instance);
return instance;
}
}
4. 实现 @Module 装饰器
模块装饰器用于组织相关的 Controller 和 Service:
typescript
function Module(metadata: { controllers: any[]; providers: any[] }) {
return function(target: any) {
defineMetadata(target, 'module', true);
defineMetadata(target, 'controllers', metadata.controllers);
defineMetadata(target, 'providers', metadata.providers);
};
}
5. 完整 Demo 示例
现在我们来实际使用这个简易框架:
typescript
// ========== 业务代码 ==========
// 1. 定义服务(可注入)
@Injectable()
class UserService {
getUsers() {
return [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
];
}
}
// 2. 定义另一个服务(依赖 UserService)
@Injectable()
class UserController {
constructor(private userService: UserService) {}
findAll() {
return this.userService.getUsers();
}
}
// 3. 定义模块
@Module({
controllers: [UserController],
providers: [UserService]
})
class AppModule {}
// ========== 启动应用 ==========
// 4. 创建容器并注册所有提供者
const container = new Container();
// 同时注册 controllers 和 providers
const moduleMetadata = {
controllers: getMetadata(AppModule, 'controllers') || [],
providers: getMetadata(AppModule, 'providers') || []
};
// 将所有类注册到容器
[...moduleMetadata.controllers, ...moduleMetadata.providers].forEach((cls: any) => {
container.addProvider(cls.name, cls);
});
// 5. 获取 Controller 实例(自动注入依赖)
const controller = container.get<UserController>('UserController');
console.log(controller.findAll());
// 输出: [ { id: 1, name: '张三' }, { id: 2, name: '李四' } ]
实际 NestJS 的实现远比这个 Demo 复杂,但核心思想完全一致:利用 TypeScript 的装饰器和反射能力,构建一个智能的依赖管理容器。