Nest源碼隨記

源碼版本:5.0.0

Nest使用入口main.ts

import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  await app.listen(3000);
}
bootstrap();

第一個(gè)對象NestFactory,源碼位置在根目錄./packages/core/nest-factory.ts。

export const NestFactory = new NestFactoryStatic();

看源碼知道,NestFactoryStatic沒有構(gòu)造器函數(shù)constructor,所以接著看create函數(shù)。
該函數(shù)使用了重載定義,直接復(fù)制最后一個(gè)。

初步了解到:

  • 第一個(gè)參數(shù)是使用是傳入的類似angularmodule的對象,
  • 第二個(gè)參數(shù)可以是設(shè)置參數(shù),也可以是類似express的MVC,如官網(wǎng)提到的fastify,覆蓋默認(rèn)使用的express的。
  • 第三個(gè)參數(shù)就只能是設(shè)置參數(shù)
public async create(
    module: any,
    serverOrOptions?: any,
    options?: NestApplicationOptions,
  ): Promise<
    INestApplication & (INestExpressApplication | INestFastifyApplication)
  > {
    const isHttpServer = serverOrOptions && serverOrOptions.patch;
    // tslint:disable-next-line:prefer-const
    let [httpServer, appOptions] = isHttpServer
      ? [serverOrOptions, options]
      : [ExpressFactory.create()/* 默認(rèn)使用Express*/, serverOrOptions];

    const applicationConfig = new ApplicationConfig();
    const container = new NestContainer(applicationConfig);
    httpServer = this.applyExpressAdapter(httpServer);

    this.applyLogger(appOptions);
    await this.initialize(module, container, applicationConfig, httpServer);
    return this.createNestInstance<NestApplication>(
      new NestApplication(container, httpServer, applicationConfig, appOptions),
    );
  }

配置options先略過,直接看ExpressFactory.create(); 返回的是new ExpressAdapter(express())。作用很簡單,只是重新轉(zhuǎn)發(fā)同樣名字的方法給express()

接著生成應(yīng)用的配置對象applicationConfig;

  private globalPipes: PipeTransform<any>[] = [];
  private globalFilters: ExceptionFilter[] = [];
  private globalInterceptors: NestInterceptor[] = [];
  private globalGuards: CanActivate[] = [];
  private globalPrefix = '';

可以知道,我們?nèi)峙渲玫墓艿馈⑦^濾器等就儲(chǔ)存到此對象中。

下一步生成容器container;傳入了配置對象

const container = new NestContainer(applicationConfig);

constructor只是保存了該配置,其它方法跳過。

下一步再次初始化http服務(wù)。

httpServer = this.applyExpressAdapter(httpServer);

若是從外部傳入的MVC對象,必須經(jīng)過ExpressAdapter再包裝。從是否有g(shù)etHttpServer方法去判斷。

接著看是否要覆蓋默認(rèn)的日志輸出對象。

this.applyLogger(options);

接著等待初始化

await this.initialize(module, container, applicationConfig);
private async initialize(
    module,
    container: NestContainer,
    config = new ApplicationConfig(),
    httpServer: HttpServer = null,
  ) {
    const instanceLoader = new InstanceLoader(container);
    const dependenciesScanner = new DependenciesScanner(
      container,
      new MetadataScanner(),
      config,
    );
    container.setApplicationRef(httpServer);
    try {
      this.logger.log(messages.APPLICATION_START);
      await ExceptionsZone.asyncRun(async () => {
        await dependenciesScanner.scan(module);
        await instanceLoader.createInstancesOfDependencies();
        dependenciesScanner.applyApplicationProviders();
      });
    } catch (e) {
      process.abort();
    }
  }

生成實(shí)例加載器和依賴掃描器。

接著容器與httpServer綁定。

接著使用內(nèi)部異常處理器(為了統(tǒng)一異常處理)異步去運(yùn)行最后的步驟。

  • 掃描當(dāng)前文件的依賴
public async scan(module: Type<any>) {
    await this.scanForModules(module);
    await this.scanModulesForDependencies();
    this.container.bindGlobalScope();
  }

scanForModules;這去掃描及保存模塊(包括imports進(jìn)來的模塊)

public async scanForModules(
    module: Type<any> | DynamicModule,
    scope: Type<any>[] = [],
  ) {
    await this.storeModule(module, scope);

    const modules = this.reflectMetadata(module, metadata.MODULES);
    for (const innerModule of modules) {
      await this.scanForModules(innerModule, [].concat(scope, module));
    }
  }

await this.storeModule(module, scope); 有使用到類似angular的forwardRef方法。

然后進(jìn)入容器對象的addModule方法里,模塊編譯器先編譯模塊。

await this.moduleCompiler.compile(
      metatype,
      scope,
    );

先統(tǒng)一一下返回的格式,對于延期加載(metatype是Promise對象的,如使用forwardRef的,先等待加載)。然后判斷是否是動(dòng)態(tài)模塊,而官網(wǎng)中動(dòng)態(tài)模塊的對象類似這樣

{
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    };

特別的動(dòng)態(tài)模塊是有獨(dú)有的module對象,所以由此判斷。

  • 非動(dòng)態(tài)模塊,直接返回{type: metatype}
  • 是動(dòng)態(tài)模塊,返回{ type/*module*/, dynamicMetadata/*其余對象*/ }

然后把type 、scope、dynamicMetadata傳入模塊token工廠(有用到映射,后期返回再看),生成唯一標(biāo)識(shí)。

最后返回模塊解析器解析返回{ type, dynamicMetadata, token }

回到容器對象的addModule方法里,往下走,根據(jù)token判斷是否已保存。沒有保存則創(chuàng)建一個(gè)Module實(shí)例。

Module對象能保存組件、注入的服務(wù)、路由、導(dǎo)出的對象及相關(guān)模塊。創(chuàng)建時(shí)先保存核心的注入(模塊的實(shí)例,模塊的配置、映射器、MVC對象、外部的上下文生成器、該模塊的的所有模塊)。

然后把Module實(shí)例保存在容器的modules對象中

接著對動(dòng)態(tài)模塊保存在dynamicModulesMetadata對象中,然后對動(dòng)態(tài)模塊里的模塊(modules\imports)都執(zhí)行下addModule方法,共用一個(gè)作用域數(shù)組

接著判斷是否是全局的模塊。判斷邏輯是去映射中找

!!Reflect.getMetadata(GLOBAL_MODULE_METADATA, metatype);

為什么可以這樣了,其實(shí)到Global的裝飾器中看看就知道:

function Global(): ClassDecorator {
  return (target: any) => {
    Reflect.defineMetadata(GLOBAL_MODULE_METADATA, true, target);
  };
}

是全局模塊則添加到globalModules的Set對象中。

現(xiàn)在上面的storeModule方法結(jié)束,

然后去映射中找外部imports進(jìn)來的模塊,要了解這一塊,需要去看module裝飾器

export function Module(metadata: ModuleMetadata): ClassDecorator {
  const propsKeys = Object.keys(metadata);

  validateKeys(propsKeys);
  showDeprecatedWarnings(metadata);
  overrideModuleMetadata(metadata);

  return (target: object) => {
    for (const property in metadata) {
      if (metadata.hasOwnProperty(property)) {
        Reflect.defineMetadata(property, metadata[property], target);
      }
    }
  };
}

function overrideModuleMetadata(moduleMetadata: ModuleMetadata) {
  moduleMetadata.modules = moduleMetadata.imports
    ? moduleMetadata.imports
    : moduleMetadata.modules;

  moduleMetadata.components = moduleMetadata.providers
    ? moduleMetadata.providers
    : moduleMetadata.components;
}

function showDeprecatedWarnings(moduleMetadata: ModuleMetadata) {
  const modulesDeprecatedWarning =
    'The "modules" key in the @Module() decorator is deprecated and will be removed within next major release. Use the "imports" key instead.';
  const componentsDeprecatetWarning =
    'The "components" key in the @Module() decorator is deprecated and will be removed within next major release. Use the "providers" key instead.';

  moduleMetadata.modules && deprecate(modulesDeprecatedWarning);
  moduleMetadata.components && deprecate(componentsDeprecatetWarning);
}

這里還能看出有在兼容以前的版本,并作警告。

外部imports的模塊將都賦值到modules對象,最后開始映射,所以能從映射中找到imports的模塊。

然后迭代imports的模塊,再次運(yùn)行scanForModules函數(shù)。

現(xiàn)在scanForModules運(yùn)行結(jié)束,把模塊都保存了。

接著掃描模塊里的依賴項(xiàng),作依賴注入的準(zhǔn)備工作,其實(shí)就是處理Module的配置項(xiàng)

await this.scanModulesForDependencies();

對獲得map類型的modules,然后迭代

  1. 映射相關(guān)的modules,其實(shí)就是綁定模塊里的外部模塊與主模塊的關(guān)系(對將廢棄的modules也是兼容)。做法是先從映射中獲得主模塊里的外部imports模塊,然后把動(dòng)態(tài)模塊(未必有)中的imports的模塊也從容器的map類型dynamicModulesMetadata(之前addModule時(shí)有設(shè)置)獲取到,把這些模塊都組成新數(shù)組。

  2. 然后迭代數(shù)組,將模塊與taken綁定。能發(fā)現(xiàn)循環(huán)依賴的判斷是迭代的值是undefined,還沒實(shí)例化,這時(shí)用forwardRef就好了。最后調(diào)用容器的addRelatedModule方法。

  3. addRelatedModule方法: 先判讀是否保存在modules對象中,沒有則返回。有則獲取出來(主模塊),然后把獲得的模塊的作用域與父作用域組成一個(gè)新數(shù)組(加上父級(jí)作用域到最后,應(yīng)該是為了讓使用了SingleScope的裝飾器的模塊在后面獲取與之前一樣的token值)。接著使用模塊編譯器獲取token值,然后去modules對象讀取對應(yīng)的Module實(shí)例(被綁定的模塊)。

  4. 運(yùn)行主模塊的addRelatedModule方法綁定被綁定的模塊related,邏輯就是保存在_relatedModules的Set類型對象中

然后到映射服務(wù)(ps:內(nèi)部方法名沒有改,但裝飾器是providers對象導(dǎo)入了)

this.reflectComponents(metatype, token);

與上面的模塊綁定一樣,先組成一個(gè)數(shù)組,然后迭代,但這次是運(yùn)行:

components.map(component => {
      this.storeComponent(component, token);
      this.reflectComponentMetadata(component, token);
      this.reflectDynamicMetadata(component, token);
    });

storeComponent保存服務(wù), 一開始的邏輯可以發(fā)現(xiàn),區(qū)分自定義服務(wù)與普通服務(wù)是一個(gè)provide屬性,普通服務(wù)沒有這個(gè)屬性的。自定義服務(wù)需要根據(jù)配置去生成服務(wù)對象。先針對provide屬性,如果是內(nèi)置的值(注冊全局的攔截器、管道、守衛(wèi)、過濾器),則需要再修改下provide的值,因?yàn)閮?nèi)置的是一個(gè)常亮值,使用Math.random().toString(36).substring(2, 32)去獲得一個(gè)隨機(jī)值,去取代provide的值,這樣的基本不會(huì)給用戶注入的服務(wù)覆蓋掉了,然后把改變后的值與之前的值及token值組成一個(gè)對象保存在applicationProvidersApplyMap數(shù)組中,方便以后對照。最后都會(huì)運(yùn)行容器的addComponent.

return this.container.addComponent(component, token);
    }

邏輯是由token獲得module,然后運(yùn)行

module.addComponent(component);

函數(shù)內(nèi)同樣要分情況處理:

  1. 非用戶自定義的服務(wù)(無provide對象): 直接保存
this._components.set((component as Type<Injectable>).name, {
      name: (component as Type<Injectable>).name,
      metatype: component as Type<Injectable>,
      instance: null,
      isResolved: false,
    });
  1. 用戶自定義的服務(wù),由addCustomProvider函數(shù)去添加,先由provide獲得name,根據(jù)第三個(gè)屬性去進(jìn)行添加
  • 對于有useClass屬性的,就是把useClass賦值給metatype
collection.set(name, {
      name,
      metatype: useClass,
      instance: null,
      isResolved: false,
    });
  • 對于有useValue屬性的:看出可以是Promise實(shí)例
collection.set(name, {
      name,
      metatype: null,
      instance: value,
      isResolved: true,
      isNotMetatype: true,
      async: value instanceof Promise,
    });
  • 對于有useFactory屬性的:isNotMetatype應(yīng)該是指不需要依賴注入,因?yàn)樽詭ё⑷肫鲗ο罅?code>inject
const { provide, name, useFactory: factory, inject } = component;
    collection.set(name, {
      name,
      metatype: factory as any,
      instance: null,
      isResolved: false,
      inject: inject || [],
      isNotMetatype: true,
    });

最后容器的addComponent返回服務(wù)的name。

然后服務(wù)迭代的下一步:

this.reflectComponentMetadata(component, token);

這個(gè)的話是對使用WebSocketGateway裝飾器里配置的middleware屬性的值再次進(jìn)行上面的storeComponent

然后服務(wù)迭代的最后一步:注入裝飾器保存的對象,裝飾器有(UseInterceptors,````UseGuards,UseFilters,UsePipes```,及參數(shù)的【太多:Query()等等等等】)

對于非參數(shù)的裝飾器,是使用如:

this.reflectInjectables(obj, token, GUARDS_METADATA);
  1. 先通過映射獲得該類別所有的值
  2. 然后把服務(wù)的原型鏈上的所有方法(除最底層的Object的方法及所有構(gòu)造器函數(shù),設(shè)置了set或的get的也不要)組成一個(gè)數(shù)組。
  3. 然后去映射中看看上面的方法有沒有也被該裝飾器裝飾了,有就,獲得映射的值并過濾出來,賦值給methodsInjectables.
  4. 然后因?yàn)檠b飾器傳入的可能是一個(gè)數(shù)組,所以要轉(zhuǎn)化成一維數(shù)組。
  5. 然后把類裝飾數(shù)組與方法裝飾數(shù)組都合成一個(gè)數(shù)組。
  6. 最后遍歷最后的數(shù)組,運(yùn)行this.storeInjectable(injectable, token) => this.container.addInjectable(component, token); => module.addInjectable(injectable);。最后的方法上面已經(jīng)說明過。

而對于參數(shù)裝飾器,因?yàn)橐部梢蕴砑庸艿?,所以需要獲得所有參數(shù)裝飾器中保存管道的pipes對象,然后運(yùn)行上面的第六點(diǎn)。

this.reflectComponents(metatype, token);結(jié)束

接著: 映射控制器

this.reflectControllers(metatype, token);

邏輯與上面映射服務(wù)的差不多,區(qū)別是最后遍歷是

this.storeRoute(route, token);
this.reflectDynamicMetadata(route, token); / 這個(gè)與服務(wù)的一樣。

storeRoute: => this.container.addController(route, token); => module.addRoute(controller); =>

this._routes.set(route.name, {
      name: route.name,
      metatype: route,
      instance: null,
      isResolved: false,
    });

其實(shí)就是上面對于非自定義的服務(wù)的保存邏輯,因?yàn)榭刂破髦荒苁且粋€(gè)Class,很好理解

this.reflectControllers(metatype, token);結(jié)束

接著映射導(dǎo)出的對象:

this.reflectExports(metatype, token);

前面邏輯類似,也是看最后遍歷:

this.storeExportedComponent(exportedComponent, token)

=>

this.container.addExportedComponent(exportedComponent, token);

=>

module.addExportedComponent(exportedComponent);

=>

public addExportedComponent(
    exportedComponent: ComponentMetatype | string | DynamicModule,
  ) {
    const addExportedUnit = (token: string) =>
      this._exports.add(this.validateExportedProvider(token));

    if (this.isCustomProvider(exportedComponent as any)) {
      return this.addCustomExportedComponent(exportedComponent as any);
    } else if (isString(exportedComponent)) {
      return addExportedUnit(exportedComponent);
    } else if (this.isDynamicModule(exportedComponent)) {
      const { module } = exportedComponent;
      return addExportedUnit(module.name);
    }
    addExportedUnit(exportedComponent.name);
  }

根據(jù)類別去添加到this._exports中;

最后添加前都要通過這個(gè)validateExportedProvider去驗(yàn)證,邏輯就是this._components有這個(gè)值,或者this._relatedModules有這個(gè)值。否則就是沒有注冊的對象,拋出錯(cuò)誤

this.reflectExports(metatype, token);結(jié)束

此時(shí)使用模塊裝飾器配置的對象全部讀取注冊完畢。

所以await this.scanModulesForDependencies();依賴注入的準(zhǔn)備完成。

接著對所有模塊綁定全局注冊的模塊

this.container.bindGlobalScope();

=>

public bindGlobalScope() {
    this.modules.forEach(module => this.bindGlobalsToRelatedModules(module));
  }

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

  public bindGlobalModuleToModule(module: Module, globalModule: Module) {
    if (module === globalModule) {
      return undefined;
    }
    module.addRelatedModule(globalModule);
  }

因?yàn)樗心K都已經(jīng)注冊完畢了,所以邏輯很簡單。

此時(shí),模塊注冊機(jī)依賴注入的準(zhǔn)備工作已經(jīng)完成。

NestFactory.initialize里的await dependenciesScanner.scan(module);完成。

接著創(chuàng)建依賴注入的實(shí)例

await instanceLoader.createInstancesOfDependencies();

=>

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

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

private createPrototypes(modules: Map<string, Module>) {
    modules.forEach(module => {
      this.createPrototypesOfComponents(module);
      this.createPrototypesOfInjectables(module);
      this.createPrototypesOfRoutes(module);
    });
  }

需要實(shí)例化的先原型實(shí)例化(構(gòu)造器無參數(shù)),可能是可以優(yōu)化速度

接著,正式開始實(shí)例化。

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

        const { name } = module.metatype;
        this.logger.log(moduleInitMessage(name));
      }),
    );
  }

第一步對模塊使用的組件進(jìn)行實(shí)例化

private async createInstancesOfComponents(module: Module) {
    await Promise.all(
      [...module.components.values()].map(
        async wrapper =>
          await this.injector.loadInstanceOfComponent(wrapper, module),
      ),
    );
  }

=>

public async loadInstanceOfComponent(
    wrapper: InstanceWrapper<Injectable>,
    module: Module,
  ) {
    const components = module.components;
    await this.loadInstance<Injectable>(wrapper, components, module);
  }

loadInstance: 首先判斷是否是Promise,是則返回wrapper.done$(其實(shí)這是從后面得出的邏輯,因?yàn)閷?shí)例的獲取是Promise的話,可以從此知道實(shí)例完成時(shí)間。)。

然后對包裝對象wrappe的對象done$賦值promise,并取出resolve,方便后期運(yùn)行promise的回調(diào)。

接著如果isResolved邏輯正的話,說明已實(shí)例話。結(jié)束方法。
否則,就要去獲取構(gòu)造器的參數(shù)去實(shí)例化wrapper。參數(shù)的注入可以在wrapper配置,但配置了的話會(huì)不再去映射中找design:paramtypes的值(構(gòu)造函數(shù)的元數(shù)據(jù)),獲取元數(shù)據(jù)時(shí),因?yàn)閰?shù)也可以配置裝飾器,所有也有可能注入,所以去映射reflectSelfParams, 替換同樣位置的元數(shù)據(jù)。
(ps: 為什么配置了通過這個(gè)值design:paramtypes可以映射得到構(gòu)造器的元數(shù)據(jù)呢,查看對應(yīng)的裝飾器(如:Injectable),是沒有相關(guān)邏輯。其實(shí)這是typescript編譯器幫忙做了,配置文件里:````"emitDecoratorMetadata": true,/有裝飾器的自動(dòng)保存目標(biāo)的構(gòu)造器的元數(shù)據(jù)在映射中,使用的key正是design:paramtypes/
"experimentalDecorators": true,/開啟裝飾器功能/```)

然后遍歷參數(shù)(注入),如果參數(shù)需要實(shí)例化的,需要再次運(yùn)行l(wèi)oadInstanceOfComponent去實(shí)例化

最后獲得的參數(shù),也會(huì)根據(jù)isResolved判斷一下是否已經(jīng)獲取實(shí)例,邏輯負(fù)的話,運(yùn)行callback去實(shí)例化,將實(shí)例賦值給currentMetatype.instance,把isResolved賦值true, 表明實(shí)例化完成,并調(diào)用上面done的resolve,這就對應(yīng)上面的邏輯, 所以這樣其它父層可以通過```wrapper.done```知道實(shí)例完成的時(shí)間,不需要再運(yùn)行一次,從而保證單例。

callback實(shí)例化邏輯,如果不是通過inject去配置的,則直接實(shí)例化,并覆蓋原來實(shí)例

currentMetatype.instance = Object.assign(
            currentMetatype.instance,
            new metatype(...instances),
          );

否則實(shí)例化方法有可能使Promise對象。需要這樣:

const factoryResult = currentMetatype.metatype(...instances);
          currentMetatype.instance = await this.resolveFactoryInstance(
            factoryResult,
          );

內(nèi)部組件實(shí)例化完成,接著服務(wù)實(shí)例化:

await this.createInstancesOfInjectables(module);

邏輯與上面:只是loadInstance的第二個(gè)參數(shù)為遍歷module.injectables的value();

接著控制器路由實(shí)例化邏輯與上面:只是loadInstance的第二個(gè)參數(shù)為遍歷module.routes的value();

此時(shí)單個(gè)module里所有實(shí)例化完成,最后log打印信息。然后繼續(xù)遍歷。

NestFactory.initialize里的await instanceLoader.createInstancesOfDependencies();完成。

接著注冊應(yīng)用的(全局)的服務(wù)提供者,之前我們在map中保存的對應(yīng)關(guān)系,通過moduleKey及providerKey找到實(shí)例,然后傳入服務(wù)提供者的初始化函數(shù),注冊完畢。

到此NestFactory.initialize完成。回到ceate, 剩最后一步:

return this.createNestInstance<NestApplication>(
      new NestApplication(container, httpServer, applicationConfig, appOptions),
    );

把之前準(zhǔn)備好的容器、MVC實(shí)例、應(yīng)用配置、用戶配置生成一個(gè)Nest應(yīng)用實(shí)例。

關(guān)鍵:

new NestApplication(container, httpServer, applicationConfig, appOptions)

構(gòu)造器:

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

this.applyOptions();/*查看配置,可以開啟cors跨域*/
this.selectContextModule();/*獲得container的第一個(gè)module,就是入口module*/
this.registerHttpServer();/*注冊MVC成http服務(wù)或https服務(wù)*/

this.routesResolver = new RoutesResolver(this.container, this.config);/* 把控制器的路由注冊到MVC上 */

}

new RoutesResolver(this.container, this.config);

=>

constructor(
    private readonly container: NestContainer,
    private readonly config: ApplicationConfig,
  ) {
    this.routerExceptionsFilter = new RouterExceptionFilters(
      container,
      config,
      container.getApplicationRef()/* 獲得MVC對象 */,
    );
    this.routerBuilder = new RouterExplorer(
      new MetadataScanner(),
      this.container,
      this.routerProxy,
      this.routerExceptionsFilter,
      this.config,
    );
  }

創(chuàng)建路由異常處理器和邏輯處理器(處理各種裝飾器)。

然后運(yùn)行createNestInstance方法,其實(shí)是新建一個(gè)代理去代理實(shí)例,禁止了寫操作及讀取原型鏈的屬性,對于函數(shù)屬性,用異常處理包裝去運(yùn)行。

目前入口const app = await NestFactory.create(ApplicationModule);已完成。

最后這個(gè)

await app.listen(3000);
// nest-application.ts

public async listen(port: number | string, callback?: () => void);
  public async listen(
    port: number | string,
    hostname: string,
    callback?: () => void,
  );
  public async listen(port: number | string, ...args) {
    !this.isInitialized && (await this.init());

    this.httpServer.listen(port, ...args);
    return this.httpServer;
  }

關(guān)鍵邏輯在await this.init()

// nest-application.ts

public async init(): Promise<this> {
    const useBodyParser =
      this.appOptions && this.appOptions.bodyParser !== false;
    useBodyParser && this.registerParserMiddleware();/* 首先判斷是否要加載 BodyParser 中間件,個(gè)人覺得這個(gè)可以放到applyOptions里*/

    await this.registerModules();
    await this.registerRouter();
    await this.callInitHook();

    this.isInitialized = true;
    this.logger.log(messages.APPLICATION_READY);
    return this;
  }

await this.registerModules(); 可注冊socket、微服務(wù)、和中間件

暫時(shí)只看中間件的注冊, 這是準(zhǔn)備工作,只是保存在中間件容器的對象中,非注冊到MVC中:

// middleware-module.ts

public async resolveMiddleware(
    middlewareContainer: MiddlewareContainer,
    modules: Map<string, Module>,
  ) {
    await Promise.all(
      [...modules.entries()].map(async ([name, module]) => {
        const instance = module.instance;

        this.loadConfiguration(middlewareContainer, instance, name);
        await this.resolver.resolveInstances(module, name);
      }),
    );
  }

public loadConfiguration(
    middlewareContainer: MiddlewareContainer,
    instance: NestModule,
    module: string,
  ) {
    if (!instance.configure) return; /* 中間件是在實(shí)例原型鏈上的configure方面,沒有的話,說明該模塊沒有配置中間件 */

    const middlewareBuilder = new MiddlewareBuilder(this.routesMapper);
    instance.configure(middlewareBuilder);

    if (!(middlewareBuilder instanceof MiddlewareBuilder)) return; /* 放置configure函數(shù)修改此變量后報(bào)錯(cuò) */

    const config = middlewareBuilder.build();
    middlewareContainer.addConfig(config, module);
  }

中間件配置例子:

export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('/cats');
  }
}

從例子可以看到,需要傳入MiddlewareConsumer類型的實(shí)例,而MiddlewareBuilder是以其為接口實(shí)現(xiàn)的。所以接下來看MiddlewareBuilderapply函數(shù)

// packages\core\middleware\builder.ts

public apply(
    ...middleware: Array<Type<any> | Function | any>,
  ): MiddlewareConfigProxy {
    return new MiddlewareBuilder.ConfigProxy(this, flatten(middleware));
  }

中間件轉(zhuǎn)成一維數(shù)組,然后使用代理,先對中間過濾非function及把非Class的轉(zhuǎn)化為Class結(jié)構(gòu)(方便后期實(shí)例化)。

// packages\core\middleware\builder.ts

public forRoutes(
      ...routes: Array<string | Type<any> | RouteInfo>,
    ): MiddlewareConsumer {
      const {
        middlewareCollection,
        bindValuesToResolve,
        routesMapper,
      } = this.builder;

      const forRoutes = this.mapRoutesToFlatList(
        routes.map(route => routesMapper.mapRouteToRouteInfo(route)),
      );
      const configuration = {
        middleware: bindValuesToResolve(
          this.includedRoutes,
          this.contextParameters,
        ),
        forRoutes: forRoutes.filter(route => !this.isRouteExcluded(route)),
      };
      middlewareCollection.add(configuration);
      return this.builder;
    }

先獲得配置了中間間的路由信息(路徑、http請求類型、回調(diào)方法(可空)、回調(diào)方法名(可空))

\\ packages\core\middleware\routes-mapper.ts

public mapRouteToRouteInfo(
    route: Type<any> | RouteInfo | string,
  ): RouteInfo[] {
    if (isString(route)) {
      return [
        {
          path: this.validateRoutePath(route),
          method: RequestMethod.ALL,
        },
      ];
    }
    const routePath: string = Reflect.getMetadata(PATH_METADATA, route);
    if (this.isRouteInfo(routePath, route)) {
      return [
        {
          path: this.validateRoutePath(route.path),
          method: route.method,
        },
      ];
    }
    const paths = this.routerExplorer.scanForPaths(
      Object.create(route),
      route.prototype,
    );
    return paths.map(item => ({
      path:
        this.validateGlobalPath(routePath) + this.validateRoutePath(item.path),
      method: item.requestMethod,
    }));
  }

從源碼可以知道,forRoutes可以接受三種類型的

  1. 字符串類型,直接為路由路徑,接受所有請求類型。返回
[
        {
          path: this.validateRoutePath(route),
          method: RequestMethod.ALL,
        },
      ];
  1. 是一個(gè)配置對象,非控制器,所以映射不到路由路徑。返回:
[
        {
          path: this.validateRoutePath(route.path),
          method: route.method,/* 可通過傳入枚舉類型的RequestMethod 去定義 */
        },
      ];
  1. 是一個(gè)控制器,因?yàn)榭刂破骺捎成渎窂?,里面的方法也可以映射二?jí)路徑,方法攔截的請求類型也可以映射,所以需要一一去獲取。使用routerExplorer工具的scanForPaths。獲取邏輯是,從一級(jí)原型鏈中獲得所有的非構(gòu)造器的方法,然后一一去映射中獲取攔截的請求類型及二級(jí)路徑。返回二級(jí)路由信息對象數(shù)組,最后路由路徑要與控制器映射的一級(jí)路徑合并。
// packages\core\router\router-explorer.ts
public exploreMethodMetadata(
    instance: Controller,
    instancePrototype,
    methodName: string,
  ): RoutePathProperties {
    const targetCallback = instancePrototype[methodName];
    const routePath = Reflect.getMetadata(PATH_METADATA, targetCallback);
    if (isUndefined(routePath)) {
      return null;
    }
    const requestMethod: RequestMethod = Reflect.getMetadata(
      METHOD_METADATA,
      targetCallback,
    );
    return {
      path: this.validateRoutePath(routePath),
      requestMethod,
      targetCallback,
      methodName,
    };
  }

接著組成路由的配置項(xiàng),覆蓋中間件的resolve方法,添加可能從外部添加的參數(shù),及把上面的路由信息去除設(shè)定中為排除在外的路徑的信息。
然后把配置項(xiàng)添加到middlewareCollection。

回到loadConfiguration

const config = middlewareBuilder.build();獲取上面的配置信息。

然后調(diào)用nest-application.ts的對象去添加配置

middlewareContainer.addConfig(config, module);

容器會(huì)分模塊name保存中間件,及整體配置

回到middleware-module.ts

接著實(shí)例化中間件,也就是依賴注入

await this.registerRouter(); 注冊路由

public async registerRouter() {
    await this.registerMiddleware(this.httpAdapter);
    const prefix = this.config.getGlobalPrefix();
    const basePath = prefix ? validatePath(prefix) : '';
    this.routesResolver.resolve(this.httpAdapter, basePath);
  }

第一步,把之前的中間件綁定對應(yīng)的路由

// packages\core\middleware\middleware-module.ts

public async registerMiddleware(
    middlewareContainer: MiddlewareContainer,
    applicationRef: any,
  ) {
    const configs = middlewareContainer.getConfigs();/* 獲得中間件與路由的關(guān)系 */
    const registerAllConfigs = (
      module: string,
      middlewareConfig: MiddlewareConfiguration[],
    ) =>
      middlewareConfig.map(async (config: MiddlewareConfiguration) => {
        await this.registerMiddlewareConfig(
          middlewareContainer,
          config,
          module,
          applicationRef,
        );
      });

    await Promise.all(
      [...configs.entries()].map(async ([module, moduleConfigs]) => {
        await Promise.all(registerAllConfigs(module, [...moduleConfigs]));
      }),
    );
  }

public async registerMiddlewareConfig(
    middlewareContainer: MiddlewareContainer,
    config: MiddlewareConfiguration,
    module: string,
    applicationRef: any,
  ) {
    const { forRoutes } = config;
    await Promise.all(
      forRoutes.map(async (routeInfo: RouteInfo) => {
        await this.registerRouteMiddleware(
          middlewareContainer,
          routeInfo,
          config,
          module,
          applicationRef,
        );
      }),
    );
  }

public async registerRouteMiddleware(
    middlewareContainer: MiddlewareContainer,
    routeInfo: RouteInfo,
    config: MiddlewareConfiguration,
    module: string,
    applicationRef: any,
  ) {
    const middlewareCollection = [].concat(config.middleware);
    await Promise.all(
      middlewareCollection.map(async (metatype: Type<NestMiddleware>) => {
        const collection = middlewareContainer.getMiddleware(module);
        const middleware = collection.get(metatype.name);
        if (isUndefined(middleware)) {
          throw new RuntimeException();
        }

        const { instance } = middleware as MiddlewareWrapper;
        await this.bindHandler(
          instance,
          metatype,
          applicationRef,
          routeInfo.method,
          routeInfo.path,
        );
      }),
    );
  }

private async bindHandler(
    instance: NestMiddleware,
    metatype: Type<NestMiddleware>,
    applicationRef: HttpServer,
    method: RequestMethod,
    path: string,
  ) {
    if (isUndefined(instance.resolve)) {
      throw new InvalidMiddlewareException(metatype.name);
    }
    const exceptionsHandler = this.routerExceptionFilter.create(
      instance,
      instance.resolve,
      undefined,
    );
    const router = applicationRef.createMiddlewareFactory(method);/* 獲得MVC的對應(yīng)方法 */
    const bindWithProxy = middlewareInstance =>
      this.bindHandlerWithProxy(
        exceptionsHandler,
        router,
        middlewareInstance,
        path,
      );
    const resolve = instance.resolve();

    if (!(resolve instanceof Promise)) {
      bindWithProxy(resolve);
      return;
    }
    const middleware = await resolve;
    bindWithProxy(middleware);
  }

private bindHandlerWithProxy(
    exceptionsHandler: ExceptionsHandler,
    router: (...args) => void,
    middleware: (req, res, next) => void,
    path: string,
  ) {
    const proxy = this.routerProxy.createProxy(middleware, exceptionsHandler);/* 代理一樣,為了處理異常 */
    const prefix = this.config.getGlobalPrefix();
    const basePath = prefix ? validatePath(prefix) : '';
    router(basePath + path, proxy); /* 中間件注冊成功 */
  }

接著是注冊路由主體了

this.routesResolver.resolve(this.httpAdapter, basePath);
// nest\packages\core\router\routes-resolver.ts

public resolve(appInstance, basePath: string) {
    const modules = this.container.getModules();
    modules.forEach(({ routes, metatype }, moduleName) => {
      let path = metatype
        ? Reflect.getMetadata(MODULE_PATH, metatype)
        : undefined;
      path = path ? path + basePath : basePath;
      this.registerRouters(routes, moduleName, path, appInstance);
    });
    this.registerNotFoundHandler();
    this.registerExceptionHandler();
  }

MODULE_PATH應(yīng)該是module上配置的前綴,但在源碼上找不到對應(yīng)設(shè)置的地方。

接著注冊路由

public registerRouters(
    routes: Map<string, InstanceWrapper<Controller>>,
    moduleName: string,
    basePath: string,
    appInstance: HttpServer,
  ) {
    routes.forEach(({ instance, metatype }) => {
// 根據(jù)Controller裝飾器獲得該controller實(shí)例的前綴
      const path = this.routerBuilder.extractRouterPath(metatype, basePath);
      const controllerName = metatype.name;
// 打印匹配信息
      this.logger.log(controllerMappingMessage(controllerName, path));
      this.routerBuilder.explore(
        instance,
        metatype,
        moduleName,
        appInstance,
        path,
      );
    });
  }
//  nest\packages\core\router\router-explorer.ts

public explore(
    instance: Controller,// 實(shí)例
    metatype: Type<Controller>,// 構(gòu)造器
    module: string,// module名
    appInstance,// MVC實(shí)例
    basePath: string, // 前綴
  ) {
    const routerPaths = this.scanForPaths(instance);
    this.applyPathsToRouterProxy(
      appInstance,
      routerPaths,
      instance,
      module,
      basePath,
    );
  }

// 從原型方法的裝飾器中查找請求方法及路徑,并返回關(guān)系對象
public scanForPaths(instance: Controller, prototype?): RoutePathProperties[] {
    const instancePrototype = isUndefined(prototype)
      ? Object.getPrototypeOf(instance)
      : prototype;
    return this.metadataScanner.scanFromPrototype<
      Controller,
      RoutePathProperties
    >(instance, instancePrototype, method =>
      this.exploreMethodMetadata(instance, instancePrototype, method),
    );
  }

public applyPathsToRouterProxy(
    router,
    routePaths: RoutePathProperties[],
    instance: Controller,
    module: string,
    basePath: string,
  ) {
    (routePaths || []).map(pathProperties => {
      const { path, requestMethod } = pathProperties;
      this.applyCallbackToRouter(
        router,
        pathProperties,
        instance,
        module,
        basePath,
      );
// 打印次級(jí)路由匹配信息
      this.logger.log(routeMappedMessage(path, requestMethod));
    });
  }

private applyCallbackToRouter(
    router,
    pathProperties: RoutePathProperties,
    instance: Controller,
    module: string,
    basePath: string,
  ) {
    const { path, requestMethod, targetCallback, methodName } = pathProperties;
    const routerMethod = this.routerMethodFactory
      .get(router, requestMethod)
      .bind(router);

// 包裝代理,為了處理異常及其它裝飾器配置
    const proxy = this.createCallbackProxy(
      instance,
      targetCallback,
      methodName,
      module,
      requestMethod,
    );
    const stripSlash = str =>
      str[str.length - 1] === '/' ? str.slice(0, str.length - 1) : str;
    const fullPath = stripSlash(basePath) + path;
    routerMethod(stripSlash(fullPath) || '/', proxy);// 注冊成功
  }

我們再看看包裝代理里的

private createCallbackProxy(
    instance: Controller,
    callback: RouterProxyCallback,
    methodName: string,
    module: string,
    requestMethod,
  ) {
    const executionContext = this.executionContextCreator.create(
      instance,
      callback,
      methodName,
      module,
      requestMethod,
    );
    const exceptionFilter = this.exceptionsFilter.create(
      instance,
      callback,
      module,
    );
    return this.routerProxy.createProxy(executionContext, exceptionFilter);
  }

executionContextCreator為路由方法添加配置的管道、看守器、攔截器,及注入需要的參數(shù)。

// packages\core\router\router-execution-context.ts

public create(
    instance: Controller,
    callback: (...args) => any,
    methodName: string,
    module: string,
    requestMethod: RequestMethod,
  ) {
    const metadata = this.reflectCallbackMetadata(instance, methodName) || {}; // 獲得參數(shù)裝飾器的保存信息,如{"query:1":{index:0,data: 0} }(@Query())
    const keys = Object.keys(metadata);
    const argsLength = this.getArgumentsLength(keys, metadata); // 獲得對象中最大的index+1值,就是參數(shù)的總數(shù)量
    const pipes = this.pipesContextCreator.create(instance, callback, module); // 獲得管道方法的數(shù)組,從下標(biāo)0開始順序是全局管道,控制器管道,方法管道
    const paramtypes = this.reflectCallbackParamtypes(instance, methodName); // 獲得原始參數(shù)類型
    const guards = this.guardsContextCreator.create(instance, callback, module); // 獲得看守器的數(shù)組
    const interceptors = this.interceptorsContextCreator.create(
      instance,
      callback,
      module,
    );// 獲得攔截器的數(shù)組
    const httpCode = this.reflectHttpStatusCode(callback);獲得需要返回的httpCode
// 下面展開說
    const paramsMetadata = this.exchangeKeysForValues(keys, metadata, module);
    const isResponseHandled = paramsMetadata.some(
      ({ type }) =>
        type === RouteParamtypes.RESPONSE || type === RouteParamtypes.NEXT,
    );// 判斷是否是請求處理方法
    const paramsOptions = this.mergeParamsMetatypes(paramsMetadata, paramtypes); // 是覆蓋傳入的對象
    const httpStatusCode = httpCode
      ? httpCode
      : this.responseController.getStatusByMethod(requestMethod);// 獲得回復(fù)的httpCode

下面展開
    const fnCanActivate = this.createGuardsFn(guards, instance, callback);
    const fnApplyPipes = this.createPipesFn(pipes, paramsOptions);
    const fnHandleResponse = this.createHandleResponseFn(
      callback,
      isResponseHandled,
      httpStatusCode,
    );
    const handler = (args, req, res, next) => async () => {
      fnApplyPipes && (await fnApplyPipes(args, req, res, next));
      return callback.apply(instance, args);
    };

public exchangeKeysForValues(
    keys: string[],
    metadata: RouteParamsMetadata,
    moduleContext: string,
  ): ParamProperties[] {
    this.pipesContextCreator.setModuleContext(moduleContext);
    return keys.map(key => {
      const { index, data, pipes: pipesCollection } = metadata[key];
      const pipes = this.pipesContextCreator.createConcreteContext(
        pipesCollection,
      );
      const type = this.mapParamType(key);

      if (key.includes(CUSTOM_ROUTE_AGRS_METADATA)) {
        const { factory } = metadata[key];
        const customExtractValue = this.getCustomFactory(factory, data);
        return { index, extractValue: customExtractValue, type, data, pipes };
      }
      const nType = Number(type);
      const extractValue = (req, res, next) =>
        this.paramsFactory.exchangeKeyForValue(nType, data, { req, res, next });
      return { index, extractValue, type: nType, data, pipes };
    });
  }

上面是路由參數(shù)的轉(zhuǎn)換,首先我們要了解參數(shù)裝飾器的用法,如Query

export function Query();
export function Query(...pipes: (Type<PipeTransform> | PipeTransform)[]);
export function Query(
  property: string,
  ...pipes: (Type<PipeTransform> | PipeTransform)[],
);
export function Query(
  property?: string | (Type<PipeTransform> | PipeTransform),
  ...pipes: (Type<PipeTransform> | PipeTransform)[],
) {
  return createPipesRouteParamDecorator(RouteParamtypes.QUERY)(
    property,
    ...pipes,
  );
}

直接使用可以獲得request.query的對象,可裝入query的對象名去獲取此value,也可以注入管道對象對query進(jìn)行轉(zhuǎn)換。

OK,回到exchangeKeysForValues方法里,

 const pipes = this.pipesContextCreator.createConcreteContext(
        pipesCollection,
      );

獲得管道的transform方法的數(shù)組。

const type = this.mapParamType(```RouteParamtypes```的枚舉值);

獲得屬性名中除位置的字段()

if (key.includes(CUSTOM_ROUTE_AGRS_METADATA)) {
        const { factory } = metadata[key]; // 拿到定義裝飾器時(shí)傳入的工廠函數(shù)
        const customExtractValue = this.getCustomFactory(factory, data); // 把裝飾器傳入的屬性加入工廠函數(shù)成一個(gè)新函數(shù)
        return { index, extractValue: customExtractValue, type, data, pipes };

public getCustomFactory(factory: (...args) => void, data): (...args) => any {
    return !isUndefined(factory) && isFunction(factory)
      ? (req, res, next) => factory(data, req)
      : () => null;
  }
      

這里是處理自定義的參數(shù)裝飾器createParamDecorator

const nType = Number(type);
      const extractValue = (req, res, next) =>
        this.paramsFactory.exchangeKeyForValue(nType, data, { req, res, next }); // 通過枚舉對應(yīng)值,就可以獲得返回的對象
      return { index, extractValue, type: nType, data, pipes };
const fnCanActivate = this.createGuardsFn(guards, instance, callback);

這是生成守護(hù)者的總函數(shù)。

最后要返回的是這個(gè)函數(shù)

async (args: any[]) => {
      const canActivate = await this.guardsConsumer.tryActivate(
        guards,
        args,
        instance,
        callback,
      );
      if (!canActivate) {
        throw new ForbiddenException(FORBIDDEN_MESSAGE);
      }
    };

tryActivate

public async tryActivate(
    guards: CanActivate[],
    args: any[],
    instance: Controller,
    callback: (...args) => any,
  ): Promise<boolean> {
    if (!guards || isEmpty(guards)) {
      return true;
    }
    const context = this.createContext(args, instance, callback);生成傳入的參數(shù)
    for (const guard of guards) {
      const result = guard.canActivate(context);
      if (await this.pickResult(result)) {
        continue;
      }
      return false;
    }
    return true;
  }

所以在守護(hù)者使用中,我們可以通過context: ExecutionContext獲得MVC路由參數(shù),控制器的構(gòu)造器及處理方法

跟著的獲得管道的總方法也差不多一樣,只是還要把之前獲得的管道與每個(gè)參數(shù)的管道合并一個(gè)數(shù)組,然后逐個(gè)去運(yùn)行對參數(shù)進(jìn)行轉(zhuǎn)換。初始value就是從參數(shù)的extractValue方法去轉(zhuǎn)換獲得。

接下來是增加路由方法的返回響應(yīng)的邏輯,因?yàn)槲覀儗懧酚商幚磉壿嫊r(shí)是多數(shù)沒有用到response對象的,這里我們?nèi)ピ黾犹幚矸祷仨憫?yīng)。

const fnHandleResponse = this.createHandleResponseFn(
      callback,
      isResponseHandled,
      httpStatusCode,
    );

public createHandleResponseFn(
    callback,
    isResponseHandled: boolean,
    httpStatusCode: number,
  ) {
    const renderTemplate = this.reflectRenderTemplate(callback);// 獲得映射中的渲染模板
    const responseHeaders = this.reflectResponseHeaders(callback);// 獲得映射中的響應(yīng)頭

    if (renderTemplate) {
      return async (result, res) => {
        this.responseController.setHeaders(res, responseHeaders);
        await this.responseController.render(result, res, renderTemplate);
      };
    }
    return async (result, res) => {
      this.responseController.setHeaders(res, responseHeaders);

      !isResponseHandled &&
        (await this.responseController.apply(result, res, httpStatusCode));
    };
  }

如果需要返回渲染的模板,則最后調(diào)用MVC的render方法,給模板傳入的變量就是我們編寫路由的返回值,就是result。

沒有模塊的話,要判斷是否是響應(yīng)處理者(就是參數(shù)中是否有傳入response或者next),有則只是設(shè)置下響應(yīng)頭,默認(rèn)不作響應(yīng),即使我們沒有使用到response的響應(yīng)方法及next(),因?yàn)闊o法判斷有沒有用。所以傳入了那些的話,要自己處理響應(yīng),不過官網(wǎng)好像也不推薦,只是為了兼容舊版本。

好了,現(xiàn)在有4個(gè)方法(守衛(wèi),管道,業(yè)務(wù)回調(diào),響應(yīng)方法)了,要按順序組裝。先守衛(wèi),然后管道處理參數(shù),然后參數(shù)傳入回調(diào),最后響應(yīng)方法。

const handler = (args, req, res, next) => async () => {
      fnApplyPipes && (await fnApplyPipes(args, req, res, next));
      return callback.apply(instance, args);
    };

    return async (req, res, next) => {
      const args = this.createNullArray(argsLength);
      fnCanActivate && (await fnCanActivate([req, res]));

      const result = await this.interceptorsConsumer.intercept(
        interceptors,
        [req, res],
        instance,
        callback,
        handler(args, req, res, next),
      );
      await fnHandleResponse(result, res); // 最后響應(yīng)
    };

但中間還有一個(gè)攔截器,若有攔截器,則需要將處理邏輯方法轉(zhuǎn)換成 stream 流,來給攔截器去操作(加工,更改等),rxjs的強(qiáng)大操作符都可以使用了。

public async intercept(
    interceptors: NestInterceptor[],
    args: any[],
    instance: Controller,
    callback: (...args) => any,
    next: () => Promise<any>,
  ): Promise<any> {
    if (!interceptors || isEmpty(interceptors)) {
      return await await next();
    }
    const context = this.createContext(args, instance, callback); // 與上面的守護(hù)一致
    const start$ = defer(() => this.transformDeffered(next)); // defer操作符是訂閱時(shí)才執(zhí)行參數(shù)方法。所以可使用緩存策略,使得減少路由業(yè)務(wù)邏輯的頻率。
    const result$ = await interceptors.reduce(
      async (stream$, interceptor) =>
        await interceptor.intercept(context, await stream$),
      Promise.resolve(start$),
    ); // 一層層數(shù)據(jù)流,可以知道,若攔截器不返回流,則無響應(yīng)。
    return await result$.toPromise();
  }

此時(shí)正常路由處理的封裝已完成,回到createCallbackProxy

\\  packages\core\router\router-explorer.ts

private createCallbackProxy(
    instance: Controller,
    callback: RouterProxyCallback,
    methodName: string,
    module: string,
    requestMethod,
  ) {
    const executionContext = this.executionContextCreator.create(
      instance,
      callback,
      methodName,
      module,
      requestMethod,
    );
    const exceptionFilter = this.exceptionsFilter.create(
      instance,
      callback,
      module,
    );
    return this.routerProxy.createProxy(executionContext, exceptionFilter);
  }

還要封裝異常過濾處理的方法。這個(gè)的create與上面的管道,守衛(wèi)等差不多,也是先獲得一個(gè)全局到控制器再到方法得過濾方法數(shù)組,只是數(shù)組內(nèi)的成員不是方法,而是一個(gè)對象,除了有一個(gè)func的方法外,還有一個(gè)exceptionMetatypes的對象,這個(gè)是指攔截的錯(cuò)誤類型。因?yàn)檫^濾器的裝飾器是可以傳參的,參數(shù)就是exceptionMetatypes。數(shù)組的獲取邏輯如下

//   packages\core\exceptions\base-exception-filter-context.ts

public createConcreteContext<T extends any[], R extends any[]>(
    metadata: T,
  ): R {
    if (isUndefined(metadata) || isEmpty(metadata)) {
      return [] as R;
    }
    return iterate(metadata)
      .filter(
        instance => instance && (isFunction(instance.catch) || instance.name),
      )
      .map(filter => this.getFilterInstance(filter))
      .map(instance => ({
        func: instance.catch.bind(instance),
        exceptionMetatypes: this.reflectCatchExceptions(instance),
      }))
      .toArray() as R;
  }

獲取過濾數(shù)組后,若數(shù)組不為空,是要覆蓋框架內(nèi)的錯(cuò)誤處理數(shù)組,還要先對數(shù)組反轉(zhuǎn),因?yàn)殄e(cuò)誤的拋出應(yīng)該是從內(nèi)到外的。

//  packages\core\router\router-exception-filters.ts

public create(
    instance: Controller,
    callback: RouterProxyCallback,
    module: string,
  ): ExceptionsHandler {
    this.moduleContext = module;

    const exceptionHandler = new ExceptionsHandler(this.applicationRef);
    const filters = this.createContext(
      instance,
      callback,
      EXCEPTION_FILTERS_METADATA,
    );
    if (isEmpty(filters)) {
      return exceptionHandler;
    }
    exceptionHandler.setCustomFilters(filters.reverse());
    return exceptionHandler;
  }

異常過濾準(zhǔn)備后了,要組合正常與過濾了。


//  packages\core\router\router-proxy.ts

public createProxy(
    targetCallback: RouterProxyCallback,
    exceptionsHandler: ExceptionsHandler,
  ) {
    return async (req, res, next) => {
      try {
        await targetCallback(req, res, next);
      } catch (e) {
        const host = new ExecutionContextHost([req, res]);
        exceptionsHandler.next(e, host);  // 這兩參數(shù)就是過濾器的參數(shù),此方法也只是從```filters```數(shù)組中find目標(biāo),去運(yùn)行,找不到就使用內(nèi)部的。
      }
    };
  }

好了router-explorer的createCallbackProxy完成,回到applyCallbackToRouter,現(xiàn)在只剩下最終的綁定路由了

//  packages\core\router\router-explorer.ts

private applyCallbackToRouter(
    router,
    pathProperties: RoutePathProperties,
    instance: Controller,
    module: string,
    basePath: string,
  ) {
    const { path, requestMethod, targetCallback, methodName } = pathProperties;
    const routerMethod = this.routerMethodFactory
      .get(router, requestMethod)
      .bind(router);

    const proxy = this.createCallbackProxy(
      instance,
      targetCallback,
      methodName,
      module,
      requestMethod,
    );
    const stripSlash = str =>
      str[str.length - 1] === '/' ? str.slice(0, str.length - 1) : str;
    const fullPath = stripSlash(basePath) + path;
    routerMethod(stripSlash(fullPath) || '/', proxy);
  }

此方法完成后會(huì)打印路由關(guān)系日志。

設(shè)置的路由已完成注冊。還要注冊路由找不到時(shí)的處理方法,及非路由邏輯的異常處理方法??聪旅娴暮竺鎯刹?/p>

//   packages\core\router\routes-resolver.ts
public resolve(appInstance, basePath: string) {
    const modules = this.container.getModules();
    modules.forEach(({ routes, metatype }, moduleName) => {
      let path = metatype
        ? Reflect.getMetadata(MODULE_PATH, metatype)
        : undefined;
      path = path ? path + basePath : basePath;
      this.registerRouters(routes, moduleName, path, appInstance);
    });
    this.registerNotFoundHandler();
    this.registerExceptionHandler();
  }

(突然發(fā)現(xiàn)丟失了最后的部分,記得保存的了。)

后面兩個(gè)異常處理簡單說說,就是自己聲明一個(gè)callback,然后調(diào)用app.use(callback)而已,一點(diǎn)都不繞。

最后調(diào)用各模塊的生命周期函數(shù)onModuleInit。

基礎(chǔ)nest應(yīng)用啟動(dòng)完成!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,578評(píng)論 19 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,942評(píng)論 1 92
  • 阿里云服務(wù)器自帶的有python,在終端查詢一下就行了: 用Xftp將python項(xiàng)目丟到服務(wù)器的某個(gè)文件夾下,然...
    紫灬楓閱讀 1,202評(píng)論 0 0
  • 人真是一個(gè)奇怪的生物。 關(guān)于童年的記憶,很多事情即使有人提醒我都記不起來了,可是故鄉(xiāng)的風(fēng)景卻像一幅...
    M夏語冰閱讀 471評(píng)論 2 3
  • 清晨,兩只蟬在樹葉間相遇 面對一顆露珠,互相謙讓 你請 你請 他們同時(shí)伸出了吸管,嘴唇相碰 在一顆水滴中接吻 他們...
    欒語閱讀 249評(píng)論 0 2

友情鏈接更多精彩內(nèi)容