DI 容器的本质:Map<token, instance>?
依赖注入(Dependency Injection,DI)容器是现代框架的核心组件之一。在 NestJS 中,DI 容器负责管理应用中所有提供者的创建、解析和注入。但 DI 容器的本质到底是什么?它真的是一个简单的 Map<token, instance> 吗?本文将深入探讨 NestJS 中 DI 容器的内部工作机制。
1. DI 容器的基本概念
1.1 什么是 DI 容器?
DI 容器是一个管理对象生命周期和依赖关系的框架组件。它负责:
- 创建对象实例
- 解析对象依赖
- 管理对象生命周期
- 提供对象注入机制
typescript
// 一个简化的 DI 容器概念
class SimpleContainer {
private instances = new Map<string, any>();
register(token: string, factory: () => any) {
this.instances.set(token, factory());
}
resolve<T>(token: string): T {
return this.instances.get(token);
}
}
// 使用示例
const container = new SimpleContainer();
container.register('UserService', () => new UserService());
const userService = container.resolve<UserService>('UserService');1.2 NestJS DI 容器的复杂性
NestJS 的 DI 容器远比上面的简单示例复杂,它需要处理:
typescript
// 复杂的依赖关系
@Injectable()
class UserService {
constructor(
private databaseService: DatabaseService,
private configService: ConfigService,
@Inject('CUSTOM_REPOSITORY') private repository: Repository,
) {}
}
@Injectable({ scope: Scope.REQUEST })
class RequestService {
// 每个请求都需要新实例
}2. NestJS 容器架构
2.1 核心组件
NestJS 的 DI 容器由以下几个核心组件构成:
typescript
// 简化的容器架构
class NestContainer {
private readonly modules = new Map<string, Module>();
private readonly providers = new Map<Token, InstanceWrapper>();
private readonly controllers = new Map<Token, InstanceWrapper>();
}
class Module {
private readonly providers = new Map<Token, InstanceWrapper>();
private readonly controllers = new Map<Token, InstanceWrapper>();
private readonly imports = new Set<Module>();
private readonly exports = new Set<Token>();
}
class InstanceWrapper {
name: string;
token: Token;
metatype: Type<any>;
scope: Scope;
instance: any;
isResolved: boolean;
// ... 其他属性
}2.2 模块化容器设计
typescript
// 每个模块都有自己的作用域
@Module({
providers: [UserService],
})
export class UserModule {}
@Module({
providers: [OrderService],
})
export class OrderModule {}
// 两个模块中的 UserService 和 OrderService 是独立的容器管理3. 提供者注册机制
3.1 静态提供者注册
typescript
@Module({
providers: [UserService], // 静态提供者
})
export class UserModule {}注册过程:
typescript
class ModulesContainer {
addProvider(provider: Provider, module: Module) {
// 1. 创建 InstanceWrapper
const wrapper = new InstanceWrapper(provider);
// 2. 根据提供者类型处理
if (this.isClassProvider(provider)) {
this.handleClassProvider(provider, wrapper);
} else if (this.isValueProvider(provider)) {
this.handleValueProvider(provider, wrapper);
} else if (this.isFactoryProvider(provider)) {
this.handleFactoryProvider(provider, wrapper);
}
// 3. 存储到模块的提供者映射中
module.providers.set(wrapper.token, wrapper);
}
}3.2 自定义提供者注册
typescript
@Module({
providers: [
// useClass - 使用不同类实现
{
provide: UserRepository,
useClass: DatabaseUserRepository,
},
// useValue - 直接提供值
{
provide: 'CONFIG',
useValue: {
host: 'localhost',
port: 3000,
},
},
// useFactory - 通过工厂函数创建
{
provide: 'CONNECTION',
useFactory: (config: ConfigService) => {
return new DatabaseConnection(config.get('DB_URL'));
},
inject: [ConfigService],
},
// useExisting - 别名提供者
{
provide: 'LOGGING_SERVICE',
useExisting: LoggerService,
},
],
})
export class UserModule {}4. 依赖解析机制
4.1 构造函数参数解析
typescript
@Injectable()
class UserService {
constructor(
private userRepository: UserRepository,
private config: ConfigService,
) {}
}解析过程:
typescript
class DependenciesScanner {
private async scanForDependencies(wrapper: InstanceWrapper) {
// 1. 获取构造函数参数类型
const tokens = this.getInjectionTokens(wrapper.metatype);
// 2. 解析每个依赖
for (const token of tokens) {
const dependency = await this.resolveDependency(token, wrapper.hostModule);
wrapper.dependencies.push(dependency);
}
}
private getInjectionTokens(metatype: Type<any>): Token[] {
// 使用 TypeScript 的反射元数据
return Reflect.getMetadata('design:paramtypes', metatype) || [];
}
}4.2 提供者查找策略
NestJS 按照以下顺序查找提供者:
typescript
class NestContainer {
getProvider(token: Token, module: Module): InstanceWrapper | null {
// 1. 查找当前模块的提供者
if (module.providers.has(token)) {
return module.providers.get(token);
}
// 2. 查找导入模块导出的提供者
for (const importedModule of module.imports) {
if (importedModule.exports.has(token) && importedModule.providers.has(token)) {
return importedModule.providers.get(token);
}
}
// 3. 查找全局提供者
if (this.globalProviders.has(token)) {
return this.globalProviders.get(token);
}
return null;
}
}5. 实例化过程
5.1 单例模式(Singleton)
默认情况下,NestJS 使用单例模式管理提供者:
typescript
class InstanceLoader {
async createInstancesOfDependencies() {
for (const [token, wrapper] of this.container.providers) {
if (!wrapper.isResolved) {
await this.resolveInstance(wrapper);
}
}
}
private async resolveInstance(wrapper: InstanceWrapper) {
// 解析依赖
const dependencies = await this.resolveDependencies(wrapper);
// 创建实例
const instance = new wrapper.metatype(...dependencies);
// 缓存实例
wrapper.instance = instance;
wrapper.isResolved = true;
}
}5.2 请求作用域(Request Scoped)
对于请求作用域的提供者,每次请求都会创建新实例:
typescript
@Injectable({ scope: Scope.REQUEST })
class UserService {
// 每个请求都会创建新实例
}
class ContextCreator {
createContext(instance: any, callback: (...args: any[]) => any, type: ContextType) {
// 对于请求作用域的实例,每次都创建新实例
if (instance.scope === Scope.REQUEST) {
return this.createContextId();
}
return STATIC_CONTEXT;
}
}6. 循环依赖处理
6.1 循环依赖问题
typescript
// user.service.ts
@Injectable()
export class UserService {
constructor(private orderService: OrderService) {} // 依赖 OrderService
}
// order.service.ts
@Injectable()
export class OrderService {
constructor(private userService: UserService) {} // 依赖 UserService
}6.2 forwardRef 解决方案
typescript
// user.service.ts
@Injectable()
export class UserService {
constructor(@Inject(forwardRef(() => OrderService)) private orderService: OrderService) {}
}
// order.service.ts
@Injectable()
export class OrderService {
constructor(@Inject(forwardRef(() => UserService)) private userService: UserService) {}
}
// user.module.ts
@Module({
providers: [UserService, OrderService],
})
export class UserModule {}实现原理:
typescript
function forwardRef(fn: () => Type<any>): ForwardReference {
return { forwardRef: fn };
}
class DependenciesScanner {
private async scanForDependencies(wrapper: InstanceWrapper) {
const tokens = this.getInjectionTokens(wrapper.metatype);
for (const token of tokens) {
if (isForwardReference(token)) {
// 延迟解析依赖
wrapper.forwardRefTokens.push(token.forwardRef());
} else {
// 立即解析依赖
const dependency = this.getProvider(token);
wrapper.dependencies.push(dependency);
}
}
}
}7. 性能优化机制
7.1 惰性实例化
NestJS 采用惰性实例化策略,只有在真正需要时才创建实例:
typescript
class InstanceLoader {
async createInstancesOfDependencies(modules: Map<string, Module> = this.container.getModules()) {
await this.createInstances(modules); // 先创建所有模块的实例
await this.createInstancesOfProviders(modules); // 再创建提供者的实例
}
private async createInstancesOfProviders(
modules: Map<string, Module>,
collectionKey: 'providers' | 'controllers' = 'providers',
) {
for (const module of modules.values()) {
// 只有在需要时才创建实例
await this.createInstancesOfProvider(module, collectionKey);
}
}
}7.2 缓存机制
NestJS 使用缓存来避免重复创建实例:
typescript
class InstanceWrapper {
instance: any = undefined;
isResolved: boolean = false;
getInstance() {
if (!this.isResolved) {
// 创建实例
this.instance = this.createInstance();
this.isResolved = true;
}
return this.instance;
}
}8. ModuleRef 的作用
8.1 动态获取提供者
typescript
@Injectable()
export class UserService {
constructor(private moduleRef: ModuleRef) {}
async someMethod() {
// 动态获取提供者
const repository = this.moduleRef.get(Repository);
// 根据上下文获取提供者
const contextId = ContextIdFactory.getByRequest(request);
const requestScopedService = await this.moduleRef.resolve(RequestScopedService, contextId);
}
}8.2 ModuleRef 实现原理
typescript
class ModuleRef {
get<T>(typeOrToken: Type<T> | string | symbol): T {
// 从当前模块获取提供者
return this.container.getProvider(typeOrToken, this.module);
}
async resolve<T>(typeOrToken: Type<T> | string | symbol, contextId?: ContextId): Promise<T> {
// 解析提供者(支持请求作用域)
const wrapper = this.container.getProvider(typeOrToken, this.module);
return this.container.getInstance(wrapper, contextId);
}
}9. 总结
NestJS 的 DI 容器远不是一个简单的 Map<token, instance>,而是一个复杂而精密的系统:
- 多层次容器结构:模块级容器 + 全局容器
- 多种提供者类型:class、value、factory、existing
- 复杂的依赖解析:支持循环依赖、跨模块依赖
- 灵活的作用域管理:singleton、request、transient
- 性能优化机制:惰性实例化、缓存机制
- 动态获取能力:ModuleRef 提供运行时获取提供者的能力
理解 DI 容器的工作原理有助于我们:
- 更好地设计应用程序的依赖关系
- 理解框架的内部工作机制
- 解决复杂的依赖注入问题
- 优化应用性能
在下一篇文章中,我们将探讨 Provider 的多种写法:useClass、useValue、useFactory、useExisting,了解它们的适用场景与内部解析逻辑。