阅读 308

Nest.js源码分析系列(一):启动机制及依赖注入

从NestFactory.create开始

从Nest.js的入口main.ts开始分析:

import { AppModule } from './app.module';
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());

  await app.listen(3001);
  console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();
复制代码

在bootstrap中首先调用了NestFactory.create(AppModule),获取app,源码位于packages/core/nest-factory.ts

// packages/core/nest-factory.ts
export class NestFactoryStatic {
	public async create<T extends INestApplication = INestApplication>(
    module: any,
    serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
    options?: NestApplicationOptions,
  ): Promise<T> {
    const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
      ? [serverOrOptions, options]
      : [this.createHttpAdapter(), serverOrOptions];

    const applicationConfig = new ApplicationConfig();
    const container = new NestContainer(applicationConfig);
    this.setAbortOnError(serverOrOptions, options);
    this.applyLogger(appOptions);
    await this.initialize(module, container, applicationConfig, httpServer);

    const instance = new NestApplication(
      container,
      httpServer,
      applicationConfig,
      appOptions,
    );
    const target = this.createNestInstance(instance);
    return this.createAdapterProxy<T>(target, httpServer);
  }
}
复制代码

在这里,我们重点关注this.initialize(module, container, applicationConfig, httpServer)的逻辑:

initialize

private async initialize(
    module: any,
    container: NestContainer,
    config = new ApplicationConfig(),
    httpServer: HttpServer = null,
  ) {
    const instanceLoader = new InstanceLoader(container);
    const metadataScanner = new MetadataScanner();
    const dependenciesScanner = new DependenciesScanner(
      container,
      metadataScanner,
      config,
    );
    container.setHttpAdapter(httpServer);

    const teardown = this.abortOnError === false ? rethrow : undefined;
    await httpServer?.init();
    try {
      this.logger.log(MESSAGES.APPLICATION_START);

      await ExceptionsZone.asyncRun(async () => {
        await dependenciesScanner.scan(module);
        await instanceLoader.createInstancesOfDependencies();
        dependenciesScanner.applyApplicationProviders();
      }, teardown);
    } catch (e) {
      this.handleInitializationError(e);
    }
  }
复制代码

在initialize中,重点关注asyncRun中的逻辑,也就是:

await ExceptionsZone.asyncRun(async () => {
        await dependenciesScanner.scan(module);
        await instanceLoader.createInstancesOfDependencies();
        dependenciesScanner.applyApplicationProviders();
      }, teardown);
复制代码

这里面做了三件事:

首先,我们来看一下scan的部分:

scan

scan部分的代码位于packages/core/scanner.ts

// packages/core/scanner.ts
export class DependenciesScanner {
  public async scan(module: Type<any>) {
    await this.registerCoreModule();
    await this.scanForModules(module);
    await this.scanModulesForDependencies();

    this.addScopedEnhancersMetadata();
    this.container.bindGlobalScope();
  }
}
复制代码

可以看到, scan里面做了一些事情,我们先从rigisterCoreModule看起:

  public async registerCoreModule() {
    const module = this.container.createCoreModule();
    const instance = await this.scanForModules(module);
    this.container.registerCoreModuleRef(instance);
  }
复制代码

rigisterCoreModule

rigisterCoreModule的逻辑并不复杂,首先调用container.crateCoreModule创建Module,通过scanForModules实例化Module,最后再通过container.registerCorModule注册:

关于NestContainer

NestContainer位于packages/core/injector/container.ts,是一个用于实现依赖注入机制的Ioc容器。在这里,我们从Module层面首先接触到了该容器。

crateCoreModule的返回

首先通过NestContainer创建的coreModule返回的是一个典型的Nest Dynamic Module,它的值如下:

module = {
  exports: [ExternalContextCreator, ModulesContainer, HttpAdapterHost],
  module: InternalCoreModule,
  providers: [{
        provide: ExternalContextCreator,
        useValue: ExternalContextCreator.fromContainer(this),
      },
      {
        provide: ModulesContainer,
        useValue: this.modules,
      },
      {
        provide: HttpAdapterHost,
        useValue: this.internalProvidersStorage.httpAdapterHost,
      }]
}
复制代码

可以看到 coreModule的provider是通过value的形式注入。

scanForModules核心逻辑

  public async scanForModules(
    module: ForwardReference | Type<unknown> | DynamicModule,
    scope: Type<unknown>[] = [],
    ctxRegistry: (ForwardReference | DynamicModule | Type<unknown>)[] = [],
  ): Promise<Module> {
    const moduleInstance = await this.insertModule(module, scope);
    ctxRegistry.push(module);

    if (this.isForwardReference(module)) {
      module = (module as ForwardReference).forwardRef();
    }
    const modules = !this.isDynamicModule(module as Type<any> | DynamicModule)
      ? this.reflectMetadata(module as Type<any>, MODULE_METADATA.IMPORTS)
      : [
          ...this.reflectMetadata(
            (module as DynamicModule).module,
            MODULE_METADATA.IMPORTS,
          ),
          ...((module as DynamicModule).imports || []),
        ];

    for (const [index, innerModule] of modules.entries()) {
      // In case of a circular dependency (ES module system), JavaScript will resolve the type to `undefined`.
      if (innerModule === undefined) {
        throw new UndefinedModuleException(module, index, scope);
      }
      if (!innerModule) {
        throw new InvalidModuleException(module, index, scope);
      }
      if (ctxRegistry.includes(innerModule)) {
        continue;
      }
      await this.scanForModules(
        innerModule,
        [].concat(scope, module),
        ctxRegistry,
      );
    }
    return moduleInstance;
  }
复制代码

scanForModules中,首先调用了insertModule方法创建了一个moduleInstance,该方法实际上是调用了container.addModule,让我们来看看addModule做了什么:

  public async addModule(
    metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
    scope: Type<any>[],
  ): Promise<Module> {
    // In DependenciesScanner#scanForModules we already check for undefined or invalid modules
    // We sill need to catch the edge-case of `forwardRef(() => undefined)`
    if (!metatype) {
      throw new UndefinedForwardRefException(scope);
    }
    const { type, dynamicMetadata, token } = await this.moduleCompiler.compile(
      metatype,
    );
    if (this.modules.has(token)) {
      return;
    }
    const moduleRef = new Module(type, this);
    this.modules.set(token, moduleRef);

    await this.addDynamicMetadata(
      token,
      dynamicMetadata,
      [].concat(scope, type),
    );

    if (this.isGlobalModule(type, dynamicMetadata)) {
      this.addGlobalModule(moduleRef);
    }
    return moduleRef;
  }
复制代码

首先,addModule调用了moduleCompiler.compile获取到token,该文件位于packages/core/injector/compiler.ts

// core/injector/compiler.ts
export class ModuleCompiler {
  public async compile(
    metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
  ): Promise<ModuleFactory> {
    const { type, dynamicMetadata } = await this.extractMetadata(metatype);
    const token = this.moduleTokenFactory.create(type, dynamicMetadata);
    return { type, dynamicMetadata, token };
  }
}
复制代码

实际上是通过moduleTokenFactory.create创建了一个token,该文件位于packages/core/injector/module-token-factory.ts

// packages/core/injector/module-token-factory.ts
export class ModuleTokenFactory {
  private readonly moduleIdsCache = new WeakMap<Type<unknown>, string>();

  public create(
    metatype: Type<unknown>,
    dynamicModuleMetadata?: Partial<DynamicModule> | undefined,
  ): string {
    const moduleId = this.getModuleId(metatype);
    const opaqueToken = {
      id: moduleId,
      module: this.getModuleName(metatype),
      dynamic: this.getDynamicMetadataToken(dynamicModuleMetadata),
    };
    return hash(opaqueToken, { ignoreUnknown: true });
  }
}
复制代码

Module token生成的逻辑如下:

接下来,addModule缓存token,并创建了一个Module对象,Module的代码在packages/core/injector/module.ts中:

// packages/core/injector/module.ts
export class Module {
  constructor(
    private readonly _metatype: Type<any>,
    private readonly container: NestContainer,
  ) {
    this.addCoreProviders();
    this._id = randomStringGenerator();
  }
}
复制代码

Module对象的创建过程中,主要执行了addCoreProviders方法:

  public addCoreProviders() {
    this.addModuleAsProvider();
    this.addModuleRef();
    this.addApplicationConfig();
  }
复制代码

这个方法的核心思想是Providers创建instanceWrapper,存储在Module的_providers数组中:

  public addModuleRef() {
    const moduleRef = this.createModuleReferenceType();
    this._providers.set(
      ModuleRef.name,
      new InstanceWrapper({
        name: ModuleRef.name,
        metatype: ModuleRef as any,
        isResolved: true,
        instance: new moduleRef(),
        host: this,
      }),
    );
  }

  public addModuleAsProvider() {
    this._providers.set(
      this._metatype.name,
      new InstanceWrapper({
        name: this._metatype.name,
        metatype: this._metatype,
        isResolved: false,
        instance: null,
        host: this,
      }),
    );
  }

  public addApplicationConfig() {
    this._providers.set(
      ApplicationConfig.name,
      new InstanceWrapper({
        name: ApplicationConfig.name,
        isResolved: true,
        instance: this.container.applicationConfig,
        host: this,
      }),
    );
  }
复制代码

至此,一个Module的创建就算完成了,scanForModules的工作就是对Module的imports字段做一个深度优先遍历,把所依赖的Module全部添加创建完成:

scanModulesForDependencies

接下来分析下scanModulesForDependencies的执行过程:

  public async scanModulesForDependencies() {
    const modules = this.container.getModules();

    for (const [token, { metatype }] of modules) {
      await this.reflectImports(metatype, token, metatype.name);
      this.reflectProviders(metatype, token);
      this.reflectControllers(metatype, token);
      this.reflectExports(metatype, token);
    }
    this.calculateModulesDistance(modules);
  }
复制代码

这个阶段主要做了两件事情,一个是将Module相关的依赖,如importsproviderscontrollersexports等,分别解析并添加,类似的方法在Module中定义:

接着执行calculateModulesDistance方法:

  public async calculateModulesDistance(modules: ModulesContainer) {
    const modulesGenerator = modules.values();
    const rootModule = modulesGenerator.next().value as Module;
    const modulesStack = [rootModule];

    const calculateDistance = (moduleRef: Module, distance = 1) => {
      if (modulesStack.includes(moduleRef)) {
        return;
      }
      modulesStack.push(moduleRef);

      const moduleImports = rootModule.relatedModules;
      moduleImports.forEach(module => {
        module.distance = distance;
        calculateDistance(module, distance + 1);
      });
    };
    calculateDistance(rootModule);
  }
复制代码

目前该方法看起来是有bug,在于rootModule被判断在ModuleStack中,直接return,而calculateDistance方法体一直得不到执行?

至此,scan阶段就告一段落。

createInstancesOfDependencies

createInstancesOfDependencies位于/packages/core/injector/instance-loader.ts中:

// packages/core/injector/instance-loader.ts
export class InstanceLoader {
  public async createInstancesOfDependencies() {
    const modules = this.container.getModules();

    this.createPrototypes(modules);
    await this.createInstances(modules);
  }
}

复制代码

该方法主要做了两件事情,首先为Modules创建instance的prototype,实现一个基于instanceWrapper的继承关系,其次去创建类的实例,根据Scan的存储结构逐步实例化,这一步也是实现依赖注入的关键逻辑。

createPrototypes的关键逻辑主要如下(位于packages/core/injector/injector.ts):

// packages/core/injector/injector.ts
  public loadPrototype<T>(
    { name }: InstanceWrapper<T>,
    collection: Map<string, InstanceWrapper<T>>,
    contextId = STATIC_CONTEXT,
  ) {
    if (!collection) {
      return;
    }
    const target = collection.get(name);
    const instance = target.createPrototype(contextId);
    if (instance) {
      const wrapper = new InstanceWrapper({
        ...target,
        instance,
      });
      collection.set(name, wrapper);
    }
  
复制代码

该方法调用intanceWrappercreatePrototype来建立一个简单的继承关系:

  public createPrototype(contextId: ContextId) {
    const host = this.getInstanceByContextId(contextId);
    if (!this.isNewable() || host.isResolved) {
      return;
    }
    return Object.create(this.metatype.prototype);
  }
复制代码

接下来是创建实例的过程:

  private async createInstances(modules: Map<string, Module>) {
    await Promise.all(
      [...modules.values()].map(async module => {
        await this.createInstancesOfProviders(module);
        await this.createInstancesOfInjectables(module);
        await this.createInstancesOfControllers(module);

        const { name } = module.metatype;
        this.isModuleWhitelisted(name) &&
          this.logger.log(MODULE_INIT_MESSAGE`${name}`);
      }),
    );
  }
复制代码

不论是Providers、Injectables、Controllers,最终都需要调用loadInstance方法来完成这一过程:

  public async loadInstance<T>(
    wrapper: InstanceWrapper<T>,
    collection: Map<string, InstanceWrapper>,
    moduleRef: Module,
    contextId = STATIC_CONTEXT,
    inquirer?: InstanceWrapper,
  ) {
    const inquirerId = this.getInquirerId(inquirer);
    const instanceHost = wrapper.getInstanceByContextId(contextId, inquirerId);
    if (instanceHost.isPending) {
      return instanceHost.donePromise;
    }
    const done = this.applyDoneHook(instanceHost);
    const { name, inject } = wrapper;

    const targetWrapper = collection.get(name);
    if (isUndefined(targetWrapper)) {
      throw new RuntimeException();
    }
    if (instanceHost.isResolved) {
      return done();
    }
    const callback = async (instances: unknown[]) => {
      const properties = await this.resolveProperties(
        wrapper,
        moduleRef,
        inject,
        contextId,
        wrapper,
        inquirer,
      );
      const instance = await this.instantiateClass(
        instances,
        wrapper,
        targetWrapper,
        contextId,
        inquirer,
      );
      this.applyProperties(instance, properties);
      done();
    };
    await this.resolveConstructorParams<T>(
      wrapper,
      moduleRef,
      inject,
      callback,
      contextId,
      wrapper,
      inquirer,
    );
  }
复制代码

我们先来看一下resolveConstructorParams的过程:

  public async resolveConstructorParams<T>(
    wrapper: InstanceWrapper<T>,
    moduleRef: Module,
    inject: InjectorDependency[],
    callback: (args: unknown[]) => void,
    contextId = STATIC_CONTEXT,
    inquirer?: InstanceWrapper,
    parentInquirer?: InstanceWrapper,
  ) {
    const inquirerId = this.getInquirerId(inquirer);
    const metadata = wrapper.getCtorMetadata();
    if (metadata && contextId !== STATIC_CONTEXT) {
      const deps = await this.loadCtorMetadata(
        metadata,
        contextId,
        inquirer,
        parentInquirer,
      );
      return callback(deps);
    }
    const dependencies = isNil(inject)
      ? this.reflectConstructorParams(wrapper.metatype as Type<any>)
      : inject;
    const optionalDependenciesIds = isNil(inject)
      ? this.reflectOptionalParams(wrapper.metatype as Type<any>)
      : [];

    let isResolved = true;
    const resolveParam = async (param: unknown, index: number) => {
      try {
        if (this.isInquirer(param, parentInquirer)) {
          return parentInquirer && parentInquirer.instance;
        }
        const paramWrapper = await this.resolveSingleParam<T>(
          wrapper,
          param,
          { index, dependencies },
          moduleRef,
          contextId,
          inquirer,
          index,
        );
        const instanceHost = paramWrapper.getInstanceByContextId(
          contextId,
          inquirerId,
        );
        if (!instanceHost.isResolved && !paramWrapper.forwardRef) {
          isResolved = false;
        }
        return instanceHost && instanceHost.instance;
      } catch (err) {
        const isOptional = optionalDependenciesIds.includes(index);
        if (!isOptional) {
          throw err;
        }
        return undefined;
      }
    };
    const instances = await Promise.all(dependencies.map(resolveParam));
    isResolved && (await callback(instances));
  }
复制代码

该方法是解析出constructor里面的参数,利用Reflect.metadata('design:paramtypes')可以非常便捷地拿到参数,针对参数进行逐步的递归遍历分析,通过resolveComponentHost方法来进行递归调用:

  public async resolveComponentHost<T>(
    moduleRef: Module,
    instanceWrapper: InstanceWrapper<T>,
    contextId = STATIC_CONTEXT,
    inquirer?: InstanceWrapper,
  ): Promise<InstanceWrapper> {
    const inquirerId = this.getInquirerId(inquirer);
    const instanceHost = instanceWrapper.getInstanceByContextId(
      contextId,
      inquirerId,
    );
    if (!instanceHost.isResolved && !instanceWrapper.forwardRef) {
      await this.loadProvider(instanceWrapper, moduleRef, contextId, inquirer);
    } else if (
      !instanceHost.isResolved &&
      instanceWrapper.forwardRef &&
      (contextId !== STATIC_CONTEXT || !!inquirerId)
    ) {
      /**
       * When circular dependency has been detected between
       * either request/transient providers, we have to asynchronously
       * resolve instance host for a specific contextId or inquirer, to ensure
       * that eventual lazily created instance will be merged with the prototype
       * instantiated beforehand.
       */
      instanceHost.donePromise &&
        instanceHost.donePromise.then(() =>
          this.loadProvider(instanceWrapper, moduleRef, contextId, inquirer),
        );
    }
    if (instanceWrapper.async) {
      const host = instanceWrapper.getInstanceByContextId(
        contextId,
        inquirerId,
      );
      host.instance = await host.instance;
      instanceWrapper.setInstanceByContextId(contextId, host, inquirerId);
    }
    return instanceWrapper;
  }
复制代码

当参数加载解析完成后,统一做实例化的操作:

  public async instantiateClass<T = any>(
    instances: any[],
    wrapper: InstanceWrapper,
    targetMetatype: InstanceWrapper,
    contextId = STATIC_CONTEXT,
    inquirer?: InstanceWrapper,
  ): Promise<T> {
    const { metatype, inject } = wrapper;
    const inquirerId = this.getInquirerId(inquirer);
    const instanceHost = targetMetatype.getInstanceByContextId(
      contextId,
      inquirerId,
    );
    const isStatic = wrapper.isStatic(contextId, inquirer);
    const isInRequestScope = wrapper.isInRequestScope(contextId, inquirer);
    const isLazyTransient = wrapper.isLazyTransient(contextId, inquirer);
    const isExplicitlyRequested = wrapper.isExplicitlyRequested(
      contextId,
      inquirer,
    );
    const isInContext =
      isStatic || isInRequestScope || isLazyTransient || isExplicitlyRequested;

    if (isNil(inject) && isInContext) {
      instanceHost.instance = wrapper.forwardRef
        ? Object.assign(
            instanceHost.instance,
            new (metatype as Type<any>)(...instances),
          )
        : new (metatype as Type<any>)(...instances);
    } else if (isInContext) {
      const factoryReturnValue = ((targetMetatype.metatype as any) as Function)(
        ...instances,
      );
      instanceHost.instance = await factoryReturnValue;
    }
    instanceHost.isResolved = true;
    return instanceHost.instance;
  }
复制代码

实例化后的结果保存在instanceHost中,至此,实例化的工作结束。

app的生成

再回到create的逻辑,当一切初始化完毕,返回一个NestApplication的实例,也就是app:

  public async create<T extends INestApplication = INestApplication>(
    module: any,
    serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
    options?: NestApplicationOptions,
  ): Promise<T> {
    const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
      ? [serverOrOptions, options]
      : [this.createHttpAdapter(), serverOrOptions];

    const applicationConfig = new ApplicationConfig();
    const container = new NestContainer(applicationConfig);
    this.setAbortOnError(serverOrOptions, options);
    this.applyLogger(appOptions);
    await this.initialize(module, container, applicationConfig, httpServer);

    const instance = new NestApplication(
      container,
      httpServer,
      applicationConfig,
      appOptions,
    );
    const target = this.createNestInstance(instance);
    return this.createAdapterProxy<T>(target, httpServer);
  }
复制代码

NestApplication位于packages/core/nest-application.ts中,包含了与httpServer(Adapter)的关联,初始化RoutesResolver等一系列逻辑,这些我们后面再分析:

// packages/core/nest-application.ts
export class NestApplication
  extends NestApplicationContext
  implements INestApplication {

  constructor(
    container: NestContainer,
    private readonly httpAdapter: HttpServer,
    private readonly config: ApplicationConfig,
    private readonly appOptions: NestApplicationOptions = {},
  ) {
    super(container);

    this.selectContextModule();
    this.registerHttpServer();

    this.routesResolver = new RoutesResolver(
      this.container,
      this.config,
      this.injector,
    );
  }
}
复制代码

依赖注入的实现总结

回顾一下Nest.js的依赖注入实现思路,主要分为三个大步骤(两个阶段):

  1. 【Scan阶段】启动程序,通过APPModule,在Scanner模块逐步寻找相关Module,构造Module依赖树
  2. 【Scan阶段】在构造Module的同时,为providers、controllers、middwares、injectables等创建instanceWrapper实例
  3. 【instance阶段】实例化过程,分析contributor构造传参,根据依赖关系从叶子节点开始逐步递归进行实例化,存储在instanceWrapper的values集合中(非DEFAULT的Scope是有多个实例的)

Scan阶段

Modules看上去的依赖关系如下:

它主要注册在Container中:

private readonly modules = new ModulesContainer();
复制代码

而ModuleContainer实际上是Module的一个Map:

export class ModulesContainer extends Map<string, Module> {}
复制代码

Module本身的数据结构中,则存储了在Module范围内的所有子组件集合:

private readonly _imports = new Set<Module>();
private readonly _providers = new Map<any, InstanceWrapper<Injectable>>();
private readonly _injectables = new Map<any, InstanceWrapper<Injectable>>();
private readonly _middlewares = new Map<any, InstanceWrapper<Injectable>>();
private readonly _controllers = new Map<
复制代码

每一个Map集合中都存储了instanceWrapper,在该Wrapper上去挂载最后的实例。

以上就是Scanner阶段的核心。

instance阶段

instance阶段的核心是——怎样拿到类的构造函数参数?

一旦确定了构造函数参数,我们就可以根据构造函数参数,来找到对应的Provider,再找出更深层次的Provider依赖,经过一层深度优先遍历,找到叶子节点的Provider,初始化整个树(实际是以数组结构存储)

获取构造函数,主要是依靠Reflect机制,获取到Metadata,根据元数据拿到参数的index及value:

// 以下是一个injectable装饰的类
@injectable
class Provider {
	constructor( private instance: Instance ) {}
}

Reflect.getMetadata('design:paramtypes', Provider)	// 拿到[instance]
复制代码

接下来,通过深度优先遍历,逐步实例化,就是该阶段的核心思想。

当然在遍历的过程中,需要注意有循环依赖的情况,Nest.js如何处理呢?我们后面文章再单独介绍。

小结

本文介绍了Nest.js的启动过程,以及Nest.js实现依赖注入的整体原理,最后总结了依赖注入实现的核心思想。依赖注入是Nest.js的核心思想,也是学习Nest.js的必经之路,希望能够帮助到有需要的同学。

文章分类
前端
文章标签