【Nest指北系列-源码】(四)NestContainer

上一篇文章中我们从整体视角讲述了 Nest 的启动流程,其中提到的第一个步骤是创建 NestContainer 容器。本章我们就来详细了解下 NestContainer,为后续学习 Nest 依赖注入系统打下基础。

注意~注意~长文预警[手动狗头]

NestContainer 类

NestContainer 是一个容器类,我们来看下这个类中维护的属性与方法。

源码位置:packages/core/injector/container.ts

属性

NestContainer 类中通过私有成员的方式维护了以下属性:

ts 复制代码
export class NestContainer {
  private readonly globalModules = new Set<Module>();
  private readonly modules = new ModulesContainer();
  private readonly dynamicModulesMetadata = new Map<
    string,
    Partial<DynamicModule>
  >();
  private readonly internalProvidersStorage = new InternalProvidersStorage();
  private readonly _serializedGraph = new SerializedGraph();
  private moduleCompiler: ModuleCompiler;
  private internalCoreModule: Module;
}

globalModules

作用 :存储通过 @Global() 装饰器声明的全局模块。

数据结构Set<Module>,全局模块的集合。

modules

作用:存储所有被注册的模块。

数据结构ModulesContainer,实际上就是 Map<string, Module>,模块 token 到模块实例的映射。

dynamicModulesMetadata

作用:存储动态模块对象。

数据结构Map<string, Partial<DynamicModule>>,模块 token 到动态模块对象的映射。

ts 复制代码
export interface DynamicModule extends ModuleMetadata {
  module: Type<any>;
  global?: boolean;
}

export interface ModuleMetadata {
  imports?: Array<
    Type<any> | DynamicModule | Promise<DynamicModule> | ForwardReference
  >;
  controllers?: Type<any>[];
  providers?: Provider[];
  exports?: Array<
    | DynamicModule
    | string
    | symbol
    | Provider
    | ForwardReference
    | Abstract<any>
    | Function
  >;
}

internalProvidersStorage

作用:存储内部使用的框架级别 Provider,主要与 http 适配器相关。

数据结构InternalProvidersStorage,一个封装类。

ts 复制代码
export class InternalProvidersStorage {
  private readonly _httpAdapterHost = new HttpAdapterHost();
  private _httpAdapter: AbstractHttpAdapter;

  get httpAdapterHost(): HttpAdapterHost {
    return this._httpAdapterHost;
  }

  get httpAdapter(): AbstractHttpAdapter {
    return this._httpAdapter;
  }

  set httpAdapter(httpAdapter: AbstractHttpAdapter) {
    this._httpAdapter = httpAdapter;
  }
}

_serializedGraph

作用:存储序列化后的模块依赖图。

数据结构SerializedGraph,一个图结构,node 表示模块,edge 表示模块之间的依赖。

ts 复制代码
export class SerializedGraph {
  private readonly nodes = new Map<string, Node>();
  private readonly edges = new Map<string, Edge>();

  public insertNode(nodeDefinition: Node) {}

  public insertEdge(edgeDefinition: WithOptionalId<Edge>) {}

  // ......
}

moduleCompiler

作用:解析模块的元数据,生成唯一的模块 token。

数据结构ModuleCompiler,一个封装类,提供解析模块的方法。

internalCoreModule

作用 :存储 Nest 框架内部的核心模块,包含 Nest 本身注入的一些 Provider,比如 Reflector 等。

数据结构Module,一个模块实例,和普通用户模块一致。

NestContainer 维护的属性的数据结构中涉及了两个比较重要的类:ModuleModuleCompiler,后续会展开分析。

方法

下面是 NestContainer 类中维护的部分常用方法。

ts 复制代码
export class NestContainer {
  public async addModule() {}

  public async addDynamicMetadata() {}

  public getModules() {}

  public getModuleByKey() {}

  public addProvider() {}

  public addController() {}

  public addImport() {}

  public bindGlobalScope() {}
}

方法可以分为以下几类:

模块注册

addModule
ts 复制代码
public async addModule(
  metatype: ModuleMetatype,
  scope: ModuleScope,
) {
  if (!metatype) {
    throw new UndefinedForwardRefException(scope);
  }
  const { type, dynamicMetadata, token } =
    await this.moduleCompiler.compile(metatype);
  if (this.modules.has(token)) {
    return {
      moduleRef: this.modules.get(token)!,
      inserted: true,
    };
  }

  return {
    moduleRef: await this.setModule(
      {
        token,
        type,
        dynamicMetadata,
      },
      scope,
    ),
    inserted: true,
  };
}

参数

  • metatype: 模块类。

  • scope: 当前模块的作用域链,用于处理依赖注入的作用域。

核心逻辑

  1. 调用 moduleCompiler.compile 解析模块元数据,得到模块唯一 token。具体如何解析会在后续对 ModuleCompiler 类的介绍中提到。

  2. 使用解析得到的模块 token 判断模块是否已经注册。

  3. 如果已注册,则直接返回已有模块实例。

  4. 如果未注册,调用 setModule 方法创建模块实例。

ts 复制代码
private async setModule(
  { token, dynamicMetadata, type }: ModuleFactory,
  scope: ModuleScope,
): Promise<Module> {
  const moduleRef = new Module(type, this);
  moduleRef.token = token;
  moduleRef.initOnPreview = this.shouldInitOnPreview(type);
  this.modules.set(token, moduleRef);

  const updatedScope = ([] as ModuleScope).concat(scope, type);
  await this.addDynamicMetadata(token, dynamicMetadata!, updatedScope);

  if (this.isGlobalModule(type, dynamicMetadata)) {
    moduleRef.isGlobal = true;

    moduleRef.distance = Number.MAX_VALUE;
    this.addGlobalModule(moduleRef);
  }

  return moduleRef;
}
  1. setModule 方法中通过 new Module() 创建模块实例(具体结构后续介绍),然后将模块实例存储到 NestContainer 实例上的 modules Map 结构中,key 为模块 token。

  2. 判断如果当前注册的模块是动态模块,将动态模块对象存储到 NestContainer 实例上的 dynamicModulesMetadata 结构中,再调用 addDynamicMetadata 方法提取动态模块 imports 中的模块并递归添加。

ts 复制代码
public async addDynamicMetadata(
  token: string,
  dynamicModuleMetadata: Partial<DynamicModule>,
  scope: Type<any>[],
) {
  if (!dynamicModuleMetadata) {
    return;
  }
  this.dynamicModulesMetadata.set(token, dynamicModuleMetadata);

  const { imports } = dynamicModuleMetadata;
  await this.addDynamicModules(imports!, scope);
}

public async addDynamicModules(modules: any[], scope: Type<any>[]) {
  if (!modules) {
    return;
  }
  await Promise.all(modules.map(module => this.addModule(module, scope)));
}
  1. 判断如果当前注册的模块是全局模块,调用 addGlobalModule 方法,额外将模块实例存储到 NestContainer 实例上的 globalModules Set 结构中。
ts 复制代码
public addGlobalModule(module: Module) {
  this.globalModules.add(module);
}

模块查找

getModules

获取所有已注册的模块。

ts 复制代码
public getModules(): ModulesContainer {
  return this.modules;
}
getModuleByKey

根据模块 token 获取已注册的模块实例。

ts 复制代码
public getModuleByKey(moduleKey: string): Module | undefined {
  return this.modules.get(moduleKey);
}

Provider 注册

addProvider

将 Provider 添加到模块中。

ts 复制代码
public addProvider(
  provider: Provider,
  token: string, // 模块 token
  enhancerSubtype?: EnhancerSubtype,
): string | symbol | Function {
  const moduleRef = this.modules.get(token);
  if (!provider) {
    throw new CircularDependencyException(moduleRef?.metatype.name);
  }
  if (!moduleRef) {
    throw new UnknownModuleException();
  }
  const providerKey = moduleRef.addProvider(provider, enhancerSubtype!);
  const providerRef = moduleRef.getProviderByKey(providerKey);

  DiscoverableMetaHostCollection.inspectProvider(this.modules, providerRef);

  return providerKey as Function;
}

参数

  • provider:支持传入各种类型的 provider,比如直接传类名、useClassuseValueuseFactoryuseExisting

  • token:目标模块 token。

核心逻辑

  1. 通过 token 拿到目标模块。

  2. 调用目标模块实例上的 moduleRef.addProvider 方法将 provider 添加到模块中。即,真正的 provider 注册是委托给 Module 类完成的Module 类的实现我们后续会讲到。

Controller 注册

addController

将 Controller 添加到模块中。

ts 复制代码
public addController(controller: Type<any>, token: string) {
  if (!this.modules.has(token)) {
    throw new UnknownModuleException();
  }
  const moduleRef = this.modules.get(token)!;
  moduleRef.addController(controller);

  const controllerRef = moduleRef.controllers.get(controller)!;
  DiscoverableMetaHostCollection.inspectController(
    this.modules,
    controllerRef,
  );
}

参数

  • controller:控制器类。

  • token:目标模块 token。

核心逻辑

  1. 通过 token 拿到目标模块。

  2. 调用目标模块实例上的 moduleRef.addController 方法将 controller 添加到模块中。即,真正的 controller 注册也是委托给 Module 类完成的

依赖关系建立

addImport

把一个子模块添加为当前模块的依赖模块。即,如果这样一个模块定义:

ts 复制代码
@Module({
  imports: [UserModule],
})
export class AppModule {}

那么 Nest 会调用 addImport(UserModule, AppModuleToken),将 UserModule 添加为 AppModule 的依赖。

看下源码:

ts 复制代码
public async addImport(
  relatedModule: Type<any> | DynamicModule,
  token: string,
) {
  if (!this.modules.has(token)) {
    return;
  }
  const moduleRef = this.modules.get(token)!;
  const { token: relatedModuleToken } =
    await this.moduleCompiler.compile(relatedModule);
  const related = this.modules.get(relatedModuleToken)!;
  moduleRef.addImport(related);
}

参数

  • relatedModule:被 imports 的模块,可以是静态类,也可以是动态模块对象。

  • token:目标模块 token。

核心逻辑

  1. 通过 token 拿到目标模块。

  2. 调用 moduleCompiler.compile 解析模块,得到模块唯一 token。

  3. 通过 token 拿到 imports 模块的引用。

  4. 调用目标模块实例上的 moduleRef.addImport 方法设置目标模块的依赖关系。即,真正的依赖关系建立是委托给 Module 类完成的

bindGlobalScope

将标记了 @Global 的全局模块标记为其他所有模块的依赖。

ts 复制代码
public bindGlobalScope() {
  this.modules.forEach(moduleRef => this.bindGlobalsToImports(moduleRef));
}

public bindGlobalsToImports(moduleRef: Module) {
  this.globalModules.forEach(globalModule =>
    this.bindGlobalModuleToModule(moduleRef, globalModule),
  );
}

public bindGlobalModuleToModule(target: Module, globalModule: Module) {
  if (target === globalModule || target === this.internalCoreModule) {
    return;
  }
  target.addImport(globalModule);
}

核心逻辑

  1. 遍历所有注册的模块,对每一个模块,遍历全局模块,调用 moduleRef.addImport 方法建立依赖关系。

  2. 遍历设置依赖关系时,排除全局模块自身和内部核心模块。

职责

通过对 NestContainer 类维护的属性与方法的分析,不难看出 NestContainer 类的主要职责如下:

  1. 模块注册与查找

  2. 管理 Provider 和 Controller

  3. 建立模块之间的依赖关系

Module 类

接下来,看一下 NestContainer 类中多次使用的 Module 类。Module 类是用于描述和封装一个模块元信息和运行时状态的内部结构。Nest 应用的每个模块在运行时都会被实例化成一个 Module 实例。

属性

Module 类中维护的属性如下:

ts 复制代码
export class Module {
  private readonly _id: string;
  private readonly _imports = new Set<Module>();
  private readonly _providers = new Map<
    InjectionToken,
    InstanceWrapper<Injectable>
  >();
  private readonly _injectables = new Map<
    InjectionToken,
    InstanceWrapper<Injectable>
  >();
  private readonly _middlewares = new Map<
    InjectionToken,
    InstanceWrapper<Injectable>
  >();
  private readonly _controllers = new Map<
    InjectionToken,
    InstanceWrapper<Controller>
  >();
  private readonly _entryProviderKeys = new Set<InjectionToken>();
  private readonly _exports = new Set<InjectionToken>();

  private _distance = 0;
  private _initOnPreview = false;
  private _isGlobal = false;
  private _token: string;
}

_providers

作用:存储该模块中注册的所有 provider。

数据结构Map<InjectionToken, InstanceWrapper<Injectable>>,一个 Map 结构,key 是 provider 的 token,value 是 provider 封装后的InstanceWrapper 实例。

InstanceWrapper 实例中包含 token、原始类、类实例、生命周期范围等信息,会在后续详细介绍。

_injectables

作用 :存储所有被 @Injectable() 装饰,参与依赖注入系统的类。即除了普通的 provider,还包括:Guard、Pipe、Interceptor、Middleware 等。

数据结构Map<InjectionToken, InstanceWrapper<Injectable>>

_controllers

作用:存储该模块中声明的控制器。

数据结构Map<InjectionToken, InstanceWrapper<Controller>>

_imports

作用 :存储该模块导入的其他模块。Nest 通过它来构建模块之间的依赖图,进行依赖注入查找

数据结构Set<Module>,模块实例的集合。

_exports

作用:存储该模块导出的 provider。

数据结构Set<InjectionToken>,provider token 的集合。

_distance

作用:记录模块之间的依赖深度。

数据结构number

_token

作用ModuleCompiler 编译模块时生成的唯一 id,用于内部模块映射。

数据结构string

_metatype

作用:当前模块的类。

数据结构 ::Type<any>

方法

ts 复制代码
export class Module {
  public addProvider() {}
  
  public addInjectable() {}

  public addController() {}

  public addImport() {}
}

addProvider

把 provider 封装成 InstanceWrapper,注册到模块的 _providers 中。

ts 复制代码
public addProvider(provider: Provider, enhancerSubtype?: EnhancerSubtype) {
  if (this.isCustomProvider(provider)) {
    if (this.isEntryProvider(provider.provide)) {
      this._entryProviderKeys.add(provider.provide);
    }
    return this.addCustomProvider(provider, this._providers, enhancerSubtype);
  }

  const isAlreadyDeclared = this._providers.has(provider);
  if (this.isTransientProvider(provider) && isAlreadyDeclared) {
    return provider;
  }

  this._providers.set(
    provider,
    new InstanceWrapper({
      token: provider,
      name: (provider as Type<Injectable>).name,
      metatype: provider as Type<Injectable>,
      instance: null,
      isResolved: false,
      scope: getClassScope(provider),
      durable: isDurable(provider),
      host: this,
    }),
  );

  if (this.isEntryProvider(provider)) {
    this._entryProviderKeys.add(provider);
  }

  return provider as Type<Injectable>;
}

参数

  • provider:支持传入各种类型的 provider,比如直接传类名、useClassuseValueuseFactoryuseExisting

核心逻辑

  1. 检查是否为自定义 provider(即 useClassuseValueuseFactoryuseExisting 形式注册的 provider)。
ts 复制代码
public isCustomProvider(
  provider: Provider,
): provider is
  | ClassProvider
  | FactoryProvider
  | ValueProvider
  | ExistingProvider {
  return !isNil(
    (
      provider as
        | ClassProvider
        | FactoryProvider
        | ValueProvider
        | ExistingProvider
    ).provide,
  );
}
  1. 如果是自定义 provider,调用 addCustomProvider 方法分类解析和处理。
ts 复制代码
public addCustomProvider(
  provider:
    | ClassProvider
    | FactoryProvider
    | ValueProvider
    | ExistingProvider,
  collection: Map<Function | string | symbol, any>,
  enhancerSubtype?: EnhancerSubtype,
) {
  if (this.isCustomClass(provider)) {
    this.addCustomClass(provider, collection, enhancerSubtype);
  } else if (this.isCustomValue(provider)) {
    this.addCustomValue(provider, collection, enhancerSubtype);
  } else if (this.isCustomFactory(provider)) {
    this.addCustomFactory(provider, collection, enhancerSubtype);
  } else if (this.isCustomUseExisting(provider)) {
    this.addCustomUseExisting(provider, collection, enhancerSubtype);
  }
  return provider.provide;
}

比如,{ provide: "xxx", useClass: MyService } 定义 provider 的场景:

  • provide 字段的值为 token
  • useClass 字段传入的类的 name 值为 name
  • useClass 字段的值为 metatype
  • instance 值为 null。

传入 InstanceWrapper 类得到实例。

ts 复制代码
public addCustomClass(
  provider: ClassProvider,
  collection: Map<InjectionToken, InstanceWrapper>,
  enhancerSubtype?: EnhancerSubtype,
) {
  let { scope, durable } = provider;

  const { useClass } = provider;
  if (isUndefined(scope)) {
    scope = getClassScope(useClass);
  }
  if (isUndefined(durable)) {
    durable = isDurable(useClass);
  }

  const token = provider.provide;
  collection.set(
    token,
    new InstanceWrapper({
      token,
      name: useClass?.name || useClass,
      metatype: useClass,
      instance: null,
      isResolved: false,
      scope,
      durable,
      host: this,
      subtype: enhancerSubtype,
    }),
  );
}

再比如,{ provide: "xxx", useValue: "xxx" } 定义 provider 的场景:

ts 复制代码
public addCustomValue(
  provider: ValueProvider,
  collection: Map<Function | string | symbol, InstanceWrapper>,
  enhancerSubtype?: EnhancerSubtype,
) {
  const { useValue: value, provide: providerToken } = provider;
  collection.set(
    providerToken,
    new InstanceWrapper({
      token: providerToken,
      name: (providerToken as Function)?.name || providerToken,
      metatype: null!,
      instance: value,
      isResolved: true,
      async: value instanceof Promise,
      host: this,
      subtype: enhancerSubtype,
    }),
  );
}
  • provide 字段的值为 token
  • provide 字段值如果是函数,取其 name 值为 name,否则取 provide 字段的值为 name
  • metatype 值为 null。
  • instance 值为 null。

传入 InstanceWrapper 类得到实例。

  1. 如果是普通 provider:
  • provider 类名为 token
  • provider 类名的 name 值为 name
  • provider 类名为 metatype
  • instance 值为 null。

传入 InstanceWrapper 类得到实例。

总结一下 :不论是什么类型的 provider,都是从 provider 的定义解析出 tokennamemetatypeinstance 等属性,传给 InstanceWrapper 得到实例,再存储到模块的 _providers生成 token -> InstanceWrapper 的映射

addInjectable

参数

  • injectable:被 @Injectable() 装饰,参与依赖注入系统的类。

核心逻辑

addProvider 基本一致,只是最终将封装后的 InstanceWrapper 实例存储到模块的 _injectables

addController

ts 复制代码
public addController(controller: Type<Controller>) {
  this._controllers.set(
    controller,
    new InstanceWrapper({
      token: controller,
      name: controller.name,
      metatype: controller,
      instance: null!,
      isResolved: false,
      scope: getClassScope(controller),
      durable: isDurable(controller),
      host: this,
    }),
  );

  this.assignControllerUniqueId(controller);
}

参数

  • controller:控制器类。

核心逻辑

addProvider 中对普通 provider 的处理基本一致。

  • controller 类名为 token
  • controller 类名的 name 值为 name
  • controller 类名为 metatype
  • instance 值为 null。

传入 InstanceWrapper 类得到实例,将实例存储到模块的 _controllers

addImport

ts 复制代码
public addImport(moduleRef: Module) {
  this._imports.add(moduleRef);
}

参数

  • moduleRefModule 实例。

核心逻辑

将传入的模块实例添加到当前模块的 _imports 集合中,形成模块之间的依赖图。

InstanceWrapper 类

在对 Module 类属性和方法的介绍中,多次提到了 InstanceWrapper,模块中的 provider、controller 等都是封装成 InstanceWrapper 实例存储在 Module 类中的。那么 InstanceWrapper 是什么呢?

InstanceWrapper 是 Nest 中一个用于管理依赖的类,简化后的核心结构长这样:

ts 复制代码
export class InstanceWrapper<T = any> {
  public readonly token: InjectionToken;
  public metatype: Type<T> | Function | null;
  public readonly name: any;
  private readonly values = new WeakMap<ContextId, InstancePerContext<T>>();
  public scope?: Scope = Scope.DEFAULT;
  public readonly host?: Module;
}

token

当前 provider 的唯一标识,可以是类、字符串、Symbol。

metatype

原始类或函数,用于实例化。

name

provider 的名字,可能是类名、函数名、useValue 场景下 provide 的 token 等。

host

记录 provider 属于哪个模块。

scope

生命周期范围,有 DEFAULT(Singleton)、REQUESTTRANSIENT

values 实例相关

一个 WeakMap 结构,存储 contextId -> InstancePerContext 的映射。

ts 复制代码
export interface InstancePerContext<T> {
  instance: T;
  isResolved?: boolean;
  isPending?: boolean;
  donePromise?: Promise<unknown>;
}
instance

实际生成的实例。大部分场景下,一开始为 null,运行时反射构造函数参数得到 wrapper 并实例化后赋值,这个实例化的过程会在下一章讲解。

isResolved

标记是否已经实例化,可以用于避免重复实例化。

isPending

标记实例是否正在构造中(异步构造或者异步生命周期钩子还在执行),用于防止多个依赖在同一时间构造导致冲突。

donePromise

一个 Promise,表示实例构造状态。

下一篇关于依赖实例化的文章中还会提及到 isPendingdonePromise

为什么需要用 Map 结构存储实例?

在默认(Singleton)作用域下,只需要一个实例就够了。但在 REQUESTTRANSIENT 模式下,一个类的实例不能全局复用,而是需要为每个请求、每次注入生成新的实例。

于是,需要使用 Map 结构存储实例,Map key 是 Nest 用来唯一标识某一个请求上下文或注入上下文的标识符。

比如:

ts 复制代码
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {}

第一个请求上下文 ID 是 ctx1,第二个请求上下文 ID 是 ctx2。每个上下文都会有自己的实例:

ts 复制代码
values = WeakMap {
  ctx1 → { instance: service1, isResolved: true },
  ctx2 → { instance: service2, isResolved: true }
}
contextId 如何生成?
  • 默认作用域,Nest 会统一使用一个固定的 contextId:STATIC_CONTEXT。即可做到全局共享同一个实例
ts 复制代码
const STATIC_CONTEXT_ID = 1;
export const STATIC_CONTEXT: ContextId = Object.freeze({
  id: STATIC_CONTEXT_ID,
});
  • REQUEST 作用域,每个请求进来时,Nest 会调用 createContextId 生成一个唯一的 contextId,它会沿调用链传递下去:
ts 复制代码
export function createContextId(): ContextId {
  return { id: Math.random() };
}
  • TRANSIENT 作用域,每次注入时会调用 createContextId 动态生成的临时 contextId。

InstanceWrapper 中封装核心结构就是这些,关于不同类型的 provider 转为 InstanceWrapper 实例时的具体属性值,在上面 Module 类的 addProvider 方法中已经有讲到。

ModuleCompiler 类

ModuleCompiler 类主要负责模块编译 ,即解析 @Module() 装饰器中的配置,得到内部需要使用的结构。比如,调用 NestContainer 类中 addModule 方法进行模块注册时,会先用 moduleCompiler.compile() 解析得到模块 token。

ModuleCompiler 的源码并不长,主要是实现了 extractMetadatacompile 两个方法,完整内容如下:

ts 复制代码
export class ModuleCompiler {
  constructor(
    private readonly _moduleOpaqueKeyFactory: ModuleOpaqueKeyFactory,
  ) {}

  get moduleOpaqueKeyFactory(): ModuleOpaqueKeyFactory {
    return this._moduleOpaqueKeyFactory;
  }

  public async compile(
    moduleClsOrDynamic:
      | Type
      | DynamicModule
      | ForwardReference
      | Promise<DynamicModule>,
  ): Promise<ModuleFactory> {
    moduleClsOrDynamic = await moduleClsOrDynamic;

    const { type, dynamicMetadata } = this.extractMetadata(moduleClsOrDynamic);
    const token = dynamicMetadata
      ? this._moduleOpaqueKeyFactory.createForDynamic(
          type,
          dynamicMetadata,
          moduleClsOrDynamic as DynamicModule | ForwardReference,
        )
      : this._moduleOpaqueKeyFactory.createForStatic(
          type,
          moduleClsOrDynamic as Type,
        );

    return { type, dynamicMetadata, token };
  }

  public extractMetadata(
    moduleClsOrDynamic: Type | ForwardReference | DynamicModule,
  ): {
    type: Type;
    dynamicMetadata: Omit<DynamicModule, 'module'> | undefined;
  } {
    if (!this.isDynamicModule(moduleClsOrDynamic)) {
      return {
        type: (moduleClsOrDynamic as ForwardReference)?.forwardRef
          ? (moduleClsOrDynamic as ForwardReference).forwardRef()
          : moduleClsOrDynamic,
        dynamicMetadata: undefined,
      };
    }
    const { module: type, ...dynamicMetadata } = moduleClsOrDynamic;
    return { type, dynamicMetadata };
  }

  public isDynamicModule(
    moduleClsOrDynamic: Type | DynamicModule | ForwardReference,
  ): moduleClsOrDynamic is DynamicModule {
    return !!(moduleClsOrDynamic as DynamicModule).module;
  }
}

extractMetadata

提取模块类和动态模块元数据。

参数

  • moduleClsOrDynamic: 普通模块类或动态模块对象。

核心逻辑

  1. 根据传入的参数上是否有 module 属性,判断传入的是普通模块类还是调用 .forRoot() 等返回的动态模块对象。

  2. 如果传入的是普通模块:返回 type 为模块类,dynamicMetadata 为 undefined。

  3. 如果传入的是动态模块对象:返回 type 为动态模块对象上的 module 属性,即动态模块类;dynamicMetadata 为动态模块元数据。

compile

ModuleCompiler 类提供给外部调用的方法,解析模块。

参数

  • moduleClsOrDynamic: 普通模块类或动态模块对象。

核心逻辑

  1. 调用 extractMetadata 得到模块类和动态模块元数据。

  2. 根据是否有动态模块元数据判断是否为动态模块,分情况生成模块 token。

  • 普通模块:随机字符串 + hash(模块类名)
ts 复制代码
`${this.generateRandomString()}:${this.hashString(moduleCls.toString())}`;
  • 动态模块:随机字符串 + hash(模块类名 + 序列化后的动态模块元数据)
ts 复制代码
`${this.generateRandomString()}:${this.hashString(moduleCls.name + JSON.stringify(dynamicMetadata))}`

最终结果类似这样:

ts 复制代码
{
  type: MyModule,
  token: 'some-unique-token-based-on-config',
  dynamicMetadata: {
    providers: [...],
    imports: [...],
    ...
  }
}

这个结构会交给 NestContainer 来注册成一个 Module 实例。

总结

根据上面的介绍,我们可以了解到 NestContainer 是用于管理整个应用程序中依赖注入的容器,但它其实不直接管理具体的 provider / controller,而是负责维护所有模块(Module),再由每个 Module 负责维护自己的 providerscontrollers 以及模块内的依赖注入信息。

本文以 NestContainer 为起点,展开介绍了 ModuleInstanceWrapperModuleCompiler 等 Nest 中的核心结构。读到这里,可能还留有一些疑问:

  • 这些结构中提供了多个方法用于生成整体的模块依赖图,比如 addModuleaddProvider 等,都是在什么时候被调用的呢?

  • 封装为 InstanceWrapper 的 provider 类,是在什么时候被实例化的呢?

下一篇关于 Nest 依赖系统的文章将会解答这些问题。

相关推荐
Eric_见嘉2 天前
NestJS 🧑‍🍳 厨子必修课(九):API 文档 Swagger
前端·后端·nestjs
XiaoYu200210 天前
第3章 Nest.js拦截器
前端·ai编程·nestjs
XiaoYu200211 天前
第2章 Nest.js入门
前端·ai编程·nestjs
实习生小黄12 天前
NestJS 调试方案
后端·nestjs
当时只道寻常15 天前
NestJS 如何配置环境变量
nestjs
濮水大叔1 个月前
VonaJS是如何做到文件级别精确HMR(热更新)的?
typescript·node.js·nestjs
ovensi1 个月前
告别笨重的 ELK,拥抱轻量级 PLG:NestJS 日志监控实战指南
nestjs
ovensi1 个月前
Docker+NestJS+ELK:从零搭建全链路日志监控系统
后端·nestjs
Gogo8161 个月前
nestjs 的项目启动
nestjs