阅读 238

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的必经之路,希望能够帮助到有需要的同学。


作者:孟健
链接:https://juejin.cn/post/6898685628042870797


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐