Nest.js源碼分析系列(一):啓動機制及依賴注入

從NestFactory.create開始

從Nest.js的入口main.ts開始分析:bootstrap

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中的邏輯,也就是:markdown

await ExceptionsZone.asyncRun(async () => {
        await dependenciesScanner.scan(module);
        await instanceLoader.createInstancesOfDependencies();
        dependenciesScanner.applyApplicationProviders();
      }, teardown);
複製代碼

這裏面作了三件事:數據結構

首先,咱們來看一下scan的部分:app

scan

scan部分的代碼位於packages/core/scanner.tsdom

// 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看起:async

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註冊:ide

關於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的必經之路,但願可以幫助到有須要的同窗。

相關文章
相關標籤/搜索