Skip to content

DI 容器的本质:Map<token, instance>?

依赖注入(Dependency Injection,DI)容器是现代框架的核心组件之一。在 NestJS 中,DI 容器负责管理应用中所有提供者的创建、解析和注入。但 DI 容器的本质到底是什么?它真的是一个简单的 Map<token, instance> 吗?本文将深入探讨 NestJS 中 DI 容器的内部工作机制。

1. DI 容器的基本概念

1.1 什么是 DI 容器?

DI 容器是一个管理对象生命周期和依赖关系的框架组件。它负责:

  1. 创建对象实例
  2. 解析对象依赖
  3. 管理对象生命周期
  4. 提供对象注入机制
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>,而是一个复杂而精密的系统:

  1. 多层次容器结构:模块级容器 + 全局容器
  2. 多种提供者类型:class、value、factory、existing
  3. 复杂的依赖解析:支持循环依赖、跨模块依赖
  4. 灵活的作用域管理:singleton、request、transient
  5. 性能优化机制:惰性实例化、缓存机制
  6. 动态获取能力:ModuleRef 提供运行时获取提供者的能力

理解 DI 容器的工作原理有助于我们:

  1. 更好地设计应用程序的依赖关系
  2. 理解框架的内部工作机制
  3. 解决复杂的依赖注入问题
  4. 优化应用性能

在下一篇文章中,我们将探讨 Provider 的多种写法:useClassuseValueuseFactoryuseExisting,了解它们的适用场景与内部解析逻辑。