Nest 源码解析:依赖注入是怎么实现的?

📕前言

Nest 是目前颇具人气的 Node.js 后端框架,在 Github 上面有 64.5k 的 Star。按照官网的说法,Nest 是 A progressive Node.js framework for building efficient, reliable and scalable server-side applications,翻译一下,也就是用于构建高效、可靠和可扩展的服务器端应用的渐进式Node.js 框架。

它基于 Typescript 开发,借助 Typescript 的装饰器特性,支持了依赖注入和控制反转,减轻类之间的耦合。此外,Nest 帮助开发者管理类的实例化和参数的注入,和 Express、Koa 等后端框架相比,给 JS 后端开发者带来了船新的开发体验。

下面学习一下 Nest 是实现依赖注入的源码,这里使用的是 10.3.4 版本的源码。

🔮设计模式

Nest 是一个支持依赖注入和控制反转的 Typescript 后端框架。依赖注入Dependency InjectionDI )是一种设计模式,符合依赖倒置原则Dependency Inversion PrincipleDIP),高层模块不应该依赖低层模块,二者都应该依赖其抽象,去除类之间的依赖关系,实现松耦合,以便于开发测试。

控制反转Ioc---Inversion of ControlIoC)也是一种设计模式,它意味着将设计好的对象交给容器控制,而不是传统的在对象内部直接控制,由框架对依赖的对象进行查找和注入,从而降低类之间的耦合,和依赖注入是一体两面的。

依赖注入和控制反转广泛应用于开发之中,在前端的 Angular、Java 的 Spring 等框架中均有实现。

从开发者使用体验的角度来说,和 Express、Koa 等需要开发者手动实现处理网络请求中间件的框架比起来,Nest 使用依赖注入,自动创建和注入各种"中间件",帮助开发者实现了实例化类、维护单例模式、解决循环依赖等逻辑。使用起来有一种在写 Spring Boot 的感觉(划去)。

举个栗子🌰: 定义一个模块 ArticleModule,这里引入了数据库模型ArticleModel,日志类ServerLogger两个模块,ArticleController处理网络请求访问,ArticleService实现业务逻辑。

ts 复制代码
@Module({
  imports: [
    SequelizeModule.forFeature([
      ArticleModel,
    ]),
    ServerLogger
  ],
  controllers: [ArticleController],
  providers: [ArticleService]
})
export class ArticleModule {}

ArticleService中自动注入了数据库模型ArticleModel,日志类ServerLogger的 Service:ServerLoggerService

ts 复制代码
@Injectable()
export class ArticleService {
  constructor (
    @InjectModel(ArticleModel) private readonly articleModel: typeof ArticleModel,
    @Inject(ServerLoggerService) private readonly logger: ServerLoggerService,
  ) {}
  test() {
    console.log(this.logger instanceof ServerLoggerService);
    console.log(this.articleModel);
  }
  // ...
}

ArticleController中,在构造函数中会自动注入ArticleService的实例:

ts 复制代码
@Controller('article')
export class ArticleController {
  constructor (
    private readonly articleService: ArticleService
  ) {}
  @Get('test')
  test() {
    console.log(this.articleService instanceof ArticleService);
    return this.articleService.test();
  }
  // ...
}

我们来访问一下 article/test,可以看到控制台输出:

可以看到注入了依赖。

🔑重要概念

✨元数据

元数据 也就是 Reflect Metadata,是 ES7 的一个提案,在 TypeScript 1.5+ 已经支持。

它可以使用Reflect.defineMetadata进行定义,使用Reflect.getMetadata进行获取,常用于实现 TS 中的装饰器。目前 TS 有 3 个内置的元数据键:

  • "design:type":类型
  • "design:paramtypes":函数入参
  • "design:returntype":函数返回值

元数据这个特性在运行时获取类型信息的场景非常有用,它增强了 TS 的类型系统,使得类型信息可以在编译后的 JS 代码中保留。例如可以把它用于反序列化和依赖注入中。

⚙模块

Nest 模块一般是这样子注册的

ts 复制代码
@Module({
  imports: [
    SequelizeModule.forFeature([
      ArticleModel,
    ]),
    ServerLogger
  ],
  controllers: [ArticleController],
  providers: [ArticleService]
})
export class ArticleModule {}

来到 packages/common/decorators/modules/module.decorator.ts,可以看到@Module装饰器的定义:

ts 复制代码
export function Module(metadata: ModuleMetadata): ClassDecorator {
  const propsKeys = Object.keys(metadata);
  validateModuleKeys(propsKeys);
  return (target: Function) => {
    for (const property in metadata) {
      if (metadata.hasOwnProperty(property)) {
        Reflect.defineMetadata(property, (metadata as any)[property], target);
      }
    }
  };
}

这个装饰器会把传入的对象添加到类的 metadata 上。依赖的扫描和注入就是利用这里的 metadata 实现的。

🧰依赖注入

在 Nest 中,@Inject@Optional装饰器用于定义构造函数参数和属性的依赖注入。

@Inject:标记构造函数参数或类属性,指示它们需要由 Nest 提供。使@Inject的参数会被记录在SELF_DECLARED_DEPS_METADATA元数据键("self:paramtypes")中,如果是属性,则记录在PROPERTY_DEPS_METADATA"self:properties_metadata")中。

ts 复制代码
export function Inject<T = any>(
  token?: T,
): PropertyDecorator & ParameterDecorator {
  return (target: object, key: string | symbol | undefined, index?: number) => {
    const type = token || Reflect.getMetadata('design:type', target, key);
    if (!isUndefined(index)) {
      let dependencies =
        Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, target) || [];
      dependencies = [...dependencies, { index, param: type }];
      Reflect.defineMetadata(SELF_DECLARED_DEPS_METADATA, dependencies, target);
      return;
    }
    let properties =
      Reflect.getMetadata(PROPERTY_DEPS_METADATA, target.constructor) || [];
    properties = [...properties, { key, type }];
    Reflect.defineMetadata(
      PROPERTY_DEPS_METADATA,
      properties,
      target.constructor,
    );
  };
}

@Optional:与@Inject一起使用,表示对应的依赖是可选的。即使没有提供相应的依赖,类也可以正常实例化。被@Optional装饰的参数会被记录在OPTIONAL_DEPS_METADATA"optional:paramtypes")。也可以用于属性上,此时记录在构造器的OPTIONAL_PROPERTY_DEPS_METADATA"optional:properties_metadata")元数据键上。

ts 复制代码
export function Optional(): PropertyDecorator & ParameterDecorator {
  return (target: object, key: string | symbol | undefined, index?: number) => {
    if (!isUndefined(index)) {
      const args = Reflect.getMetadata(OPTIONAL_DEPS_METADATA, target) || [];
      Reflect.defineMetadata(OPTIONAL_DEPS_METADATA, [...args, index], target);
      return;
    }
    const properties =
      Reflect.getMetadata(
        OPTIONAL_PROPERTY_DEPS_METADATA,
        target.constructor,
      ) || [];
    Reflect.defineMetadata(
      OPTIONAL_PROPERTY_DEPS_METADATA,
      [...properties, key],
      target.constructor,
    );
  };
}

📌入口逻辑

在 Nest 项目的入口文件,main.ts,一般可以看到:

ts 复制代码
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());

  await app.listen(3000);
  console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();

依赖注入的逻辑从NestFactory.create开始。NestFactoryNestFactoryStatic的实例。下面来看create方法,在 packages/core/nest-factory.ts 可以找到这部分的代码:

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

  const applicationConfig = new ApplicationConfig();
  // 创建 IoC 容器,用于记录和管理模块及其依赖
  const container = new NestContainer(applicationConfig);
  const graphInspector = this.createGraphInspector(appOptions, container);

  this.setAbortOnError(serverOrOptions, options);
  this.registerLoggerConfiguration(appOptions);

  // 初始化,进行扫描和注入依赖,实例化类等逻辑
  await this.initialize(
    moduleCls,
    container,
    graphInspector,
    applicationConfig,
    appOptions,
    httpServer,
  );

  // 创建 Nest 应用
  const instance = new NestApplication(
    container,
    httpServer,
    applicationConfig,
    graphInspector,
    appOptions,
  );
  const target = this.createNestInstance(instance);
  return this.createAdapterProxy<T>(target, httpServer);
}

主要来看this.initialize的逻辑:

ts 复制代码
private async initialize(
  module: any,
  container: NestContainer,
  graphInspector: GraphInspector,
  config = new ApplicationConfig(),
  options: NestApplicationContextOptions = {},
  httpServer: HttpServer = null,
) {
  UuidFactory.mode = options.snapshot
    ? UuidFactoryMode.Deterministic
    : UuidFactoryMode.Random;
  // 创建注入器
  const injector = new Injector({ preview: options.preview });
  // 创建实例加载器,用于实例化类
  const instanceLoader = new InstanceLoader(
    container,
    injector,
    graphInspector,
  );
  const metadataScanner = new MetadataScanner();
  // 创建依赖扫描器,用于获取模块及其依赖
  const dependenciesScanner = new DependenciesScanner(
    container,
    metadataScanner,
    graphInspector,
    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();
        // 扫描 App 中的 Provider
        dependenciesScanner.applyApplicationProviders();
      },
      teardown,
      this.autoFlushLogs,
    );
  } catch (e) {
    this.handleInitializationError(e);
  }
}

总之,dependenciesScanner.scan(module)进行依赖扫描,instanceLoader.createInstancesOfDependencies进行实例化和依赖注入。把入口的逻辑总结一下:

sequenceDiagram NestFactory->>NestFactory: create NestFactory->>NestFactory: initialize NestFactory->>DependenciesScanner: scan NestFactory->>InstanceLoader: createInstancesOfDependencies NestFactory->>DependenciesScanner: applyApplicationProviders

🔍依赖扫描

依赖扫描我们关注dependenciesScanner.scan,在 packages/core/scanner.ts,来看scan函数:

ts 复制代码
public async scan(
  module: Type<any>,
  options?: { overrides?: ModuleOverride[] },
) {
  // 注册内建模块
  await this.registerCoreModule(options?.overrides);
  // 扫描并在 IoC 容器中添加模块
  await this.scanForModules({
    moduleDefinition: module,
    overrides: options?.overrides,
  });
  // 处理模块的 imports、 providers、controlles、exports,并记录在 IoC 容器中
  await this.scanModulesForDependencies();
  this.calculateModulesDistance();
  this.addScopedEnhancersMetadata();
  // 把全局模块和内建模块加入到 imports 中
  this.container.bindGlobalScope();
}

主要关注this.scanForModulesthis.scanModulesForDependencies两个函数。

📑记录模块

来看scanForModules,逻辑是对模块的imports进行深度遍历,添加到 IoC 容器containermodules上。

ts 复制代码
public async scanForModules({
  moduleDefinition,
  lazy,
  scope = [],
  ctxRegistry = [],
  overrides = [],
}: ModulesScanParameters): Promise<Module[]> {
  const { moduleRef: moduleInstance, inserted: moduleInserted } =
    // 添加模块
    (await this.insertOrOverrideModule(moduleDefinition, overrides, scope)) ??
    {};
  // ...
  let registeredModuleRefs = [];
  for (const [index, innerModule] of modules.entries()) {
    if (ctxRegistry.includes(innerModule)) {
      continue;
    }
    // 深度遍历
    const moduleRefs = await this.scanForModules({
      moduleDefinition: innerModule,
      scope: [].concat(scope, moduleDefinition),
      ctxRegistry,
      overrides,
      lazy,
    });
    registeredModuleRefs = registeredModuleRefs.concat(moduleRefs);
  }
  // ...
  return [moduleInstance].concat(registeredModuleRefs);
}

添加模块的逻辑在DependenciesScanner.insertOrOverrideModuleDependenciesScanner.insertModuleNestContainer.addModule

在依赖扫描器中,有属性private readonly container: NestContainerNestContainer也就是入口逻辑中创建的 IoC 容器的类。NestContainer.modules是一个Map的数据结构。这里 IoC 容器this.container会使用this.moduleCompiler.compile给模块创建唯一的字符串标识符token并且记录在this.container.modules上。

ts 复制代码
public async addModule(
  metatype: ModuleMetatype,
  scope: ModuleScope,
): Promise<{ moduleRef: Module; inserted: boolean; } | undefined> {
  const { type, dynamicMetadata, token } =
    await this.moduleCompiler.compile(metatype);
  if (this.modules.has(token)) {
    return { moduleRef: this.modules.get(token), inserted: true };
  }
  return {
    moduleRef: await this.setModule({ token, type, dynamicMetadata }, scope),
    inserted: true,
  };
}
private async setModule(
  { token, dynamicMetadata, type }: ModuleFactory, scope: ModuleScope,
): Promise<Module | undefined> {
  const moduleRef = new Module(type, this);
  moduleRef.token = token;
  moduleRef.initOnPreview = this.shouldInitOnPreview(type);
  this.modules.set(token, moduleRef);
  const updatedScope = [].concat(scope, type);
  await this.addDynamicMetadata(token, dynamicMetadata, updatedScope);
  if (this.isGlobalModule(type, dynamicMetadata)) {
    moduleRef.isGlobal = true;
    this.addGlobalModule(moduleRef);
  }
  return moduleRef;
}

执行完scanForModules,IoC 容器的modules记录了所有的模块。小结一下:

sequenceDiagram DependenciesScanner->>DependenciesScanner: insertOrOverrideModule DependenciesScanner->>DependenciesScanner: insertModule DependenciesScanner->>NestContainer: addModule NestContainer->>NestContainer: setModule NestContainer->>ModuleCompiler: compile NestContainer->>Module: new Module NestContainer->>NestContainer: this.modules.set(token, moduleRef) DependenciesScanner->>DependenciesScanner: 深度遍历 imports

📒记录模块各组件

接下来scanModulesForDependencies记录模块的各个组件(@Module上的各属性):

ts 复制代码
public async scanModulesForDependencies(
  modules: Map<string, Module> = 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);
  }
}

这里逻辑比较繁琐,就不细说了,大概是遍历各个字段,记录所有providerscontrollersimportsexports,以及其中使用的各种中间件,下面用大概总结一下,对于this.container.modules上面每一个模块,有如下主要的字段记录了模块的各依赖:

graph LR A(module)-->B(_imports)---C("Set,记录 imports 中的模块") A-->D(_providers)---E("Map,记录 providers,为 provider: providerRef 键值对") A-->F(_controllers)---G("Map,记录 controllers,为 controller: controllerRef 键值对") A-->H(_injectables)---I("Map,记录了函数形式的\nproviders、controllers 自身及其方法上的 guard、pipe、filter、interceptor,\n以及方法中的路由依赖注入(例如 @Body、@UploadedFile 等等),\n为 injectable: instanceWrapper 键值对") A-->J(_exports)---K("Set,导出的 provider")

上面的injectable还会被添加到对应Provider或者Controller[INSTANCE_METADATA_SYMBOL]数组中。

module中,有这样子的 get 方法,这些属性会在下面实例化中用到。

ts 复制代码
get providers(): Map<InjectionToken, InstanceWrapper<Injectable>> {
  return this._providers;
}

get imports(): Set<Module> {
  return this._imports;
}

get injectables(): Map<InjectionToken, InstanceWrapper<Injectable>> {
  return this._injectables;
}

get controllers(): Map<InjectionToken, InstanceWrapper<Controller>> {
  return this._controllers;
}

get exports(): Set<InjectionToken> {
  return this._exports;
}

💉依赖注入

依赖注入的逻辑在 packages/core/injector/instance-loader.ts,先是实例化原型对象,再实例化依赖:

ts 复制代码
public async createInstancesOfDependencies(
  modules: Map<string, Module> = this.container.getModules(),
) {
  // 实例化原型
  this.createPrototypes(modules);

  try {
    // 实例化依赖
    await this.createInstances(modules);
  } catch (err) {
    this.graphInspector.inspectModules(modules);
    this.graphInspector.registerPartial(err);
    throw err;
  }
  this.graphInspector.inspectModules(modules);
}

🧬实例化原型对象

createPrototypes依次实例化providersinjectablescontrollers的原型

ts 复制代码
private createPrototypes(modules: Map<string, Module>) {
  modules.forEach(moduleRef => {
    this.createPrototypesOfProviders(moduleRef);
    this.createPrototypesOfInjectables(moduleRef);
    this.createPrototypesOfControllers(moduleRef);
  });
}
private createPrototypesOfProviders(moduleRef: Module) {
  const { providers } = moduleRef;
  providers.forEach(wrapper =>
    this.injector.loadPrototype<Injectable>(wrapper, providers),
  );
}
private createPrototypesOfInjectables(moduleRef: Module) {
  const { injectables } = moduleRef;
  injectables.forEach(wrapper =>
    this.injector.loadPrototype(wrapper, injectables),
  );
}
private createPrototypesOfControllers(moduleRef: Module) {
  const { controllers } = moduleRef;
  controllers.forEach(wrapper =>
    this.injector.loadPrototype<Controller>(wrapper, controllers),
  );
}

最终调用this.injector.loadPrototype方法,实例化原型对象,并且添加到对应的包装类的instance属性上。InstanceWrapper创建了一个有原型对象的空对象,后续实例化依赖的时候,把实例合并进来即可。

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

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

总结一下这里的流程:

graph LR A(createPrototypes) -->|1| createPrototypesOfProviders -->B(loadPrototype) -->createPrototype A -->|2| createPrototypesOfInjectables --> B A -->|3| createPrototypesOfControllers --> B

🧫实例化依赖入参

createInstances方法完成依赖的实例化:

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

      const { name } = moduleRef;
      this.isModuleWhitelisted(name) &&
        this.logger.log(MODULE_INIT_MESSAGE`${name}`);
    }),
  );
}

最终会调用this.injector.loadInstance方法:

ts 复制代码
private async createInstancesOfProviders(moduleRef: Module) {
  const { providers } = moduleRef;
  const wrappers = [...providers.values()];
  await Promise.all(
    wrappers.map(async item => {
      await this.injector.loadProvider(item, moduleRef);
      this.graphInspector.inspectInstanceWrapper(item, moduleRef);
    }),
  );
}
// ...
// packages/core/injector/injector.ts
public async loadProvider(
  wrapper: InstanceWrapper<Injectable>,
  moduleRef: Module,
  contextId = STATIC_CONTEXT,
  inquirer?: InstanceWrapper,
) {
  const providers = moduleRef.providers;
  await this.loadInstance<Injectable>(
    wrapper,
    providers,
    moduleRef,
    contextId,
    inquirer,
  );
  await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
}
// ...

省略一部分代码。loadInstancethis.resolveConstructorParams中解析构造函数参数,在callback中完成依赖的实例化。

ts 复制代码
public async loadInstance<T>(
  wrapper: InstanceWrapper<T>,
  collection: Map<InjectionToken, InstanceWrapper>,
  moduleRef: Module,
  contextId = STATIC_CONTEXT,
  inquirer?: InstanceWrapper,
) {
  // ...
  try {
    const t0 = this.getNowTimestamp();
    const callback = async (instances: unknown[]) => {
      // 完成实例化,下面再说
    };
    // 解析构造函数参数
    await this.resolveConstructorParams<T>(
      wrapper,
      moduleRef,
      inject as InjectionToken[],
      callback,
      contextId,
      wrapper,
      inquirer,
    );
  } catch (err) {
    settlementSignal.error(err);
    throw err;
  }
}

这里省略部分代码,resolveConstructorParams首先对FactoryProviderclass形式的Provider(其实这里class形式的不一定是ProviderController和各种Injectable也是走这个逻辑)进行构造函数参数的获取。

FactoryProviderthis.getFactoryProviderDependenciesclass形式的则走this.getClassDependencies

ts 复制代码
public async resolveConstructorParams<T>(
  wrapper: InstanceWrapper<T>,
  moduleRef: Module,
  inject: InjectorDependency[],
  callback: (args: unknown[]) => void | Promise<void>,
  contextId = STATIC_CONTEXT,
  inquirer?: InstanceWrapper,
  parentInquirer?: InstanceWrapper,
) {
  // ...
  const isFactoryProvider = !isNil(inject);
  const [dependencies, optionalDependenciesIds] = isFactoryProvider
    ? this.getFactoryProviderDependencies(wrapper)
    : this.getClassDependencies(wrapper);
  // ...
}

这里涉及到FactoryProvider,就简要介绍一下。平常的Provider是这样子的:

ts 复制代码
providers: [ArticleService] // ArticleService 是一个 class

实例化的入参写在它的构造函数上面。FactoryProvider是用另一种方式注册的Provider,例如:

ts 复制代码
providers: [
 {
   provide: 'ArticleService',
   useFactory: (aService: AService, tempService?: TempService) => {
     return new SomeProvider(aService, tempService);
   },
   inject: [AService, { token: TempService, optional: true }]
 },
];

它的入参就是inject数组。

对于FactoryProvider,参数取的是inject属性:

ts 复制代码
public getFactoryProviderDependencies<T>(
  wrapper: InstanceWrapper<T>,
): [InjectorDependency[], number[]] {
  const optionalDependenciesIds = [];
  // ...
  const mapFactoryProviderInjectArray = (
    item: InjectionToken | OptionalFactoryDependency,
    index: number,
  ): InjectionToken => {
    if (typeof item !== 'object') {
      return item;
    }
    if (isOptionalFactoryDep(item)) {
      if (item.optional) {
        optionalDependenciesIds.push(index);
      }
      return item?.token;
    }
    return item;
  };
  return [
    wrapper.inject?.map?.(mapFactoryProviderInjectArray),
    optionalDependenciesIds,
  ];
}

对于通常的class的形式,普通参数是通过PARAMTYPES_METADATASELF_DECLARED_DEPS_METADATA的元数据合并得到的。可选参数则是通过获取OPTIONAL_DEPS_METADATA元数据得到的。

PARAMTYPES_METADATA就是"design:paramtypes",记录了 TS 中函数的参数,SELF_DECLARED_DEPS_METADATA"self:paramtypes",它产生于@Inject的参数装饰器。OPTIONAL_DEPS_METADATA则为"optional:paramtypes",由@Optional参数装饰器产生。

ts 复制代码
public getClassDependencies<T>(
  wrapper: InstanceWrapper<T>,
): [InjectorDependency[], number[]] {
  const ctorRef = wrapper.metatype as Type<any>;
  return [
    this.reflectConstructorParams(ctorRef),
    this.reflectOptionalParams(ctorRef),
  ];
}
public reflectConstructorParams<T>(type: Type<T>): any[] {
  const paramtypes = [
    ...(Reflect.getMetadata(PARAMTYPES_METADATA, type) || []),
  ];
  const selfParams = this.reflectSelfParams<T>(type);

  selfParams.forEach(({ index, param }) => (paramtypes[index] = param));
  return paramtypes;
}

public reflectOptionalParams<T>(type: Type<T>): any[] {
  return Reflect.getMetadata(OPTIONAL_DEPS_METADATA, type) || [];
}

public reflectSelfParams<T>(type: Type<T>): any[] {
  return Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || [];
}

获取到参数后,resolveConstructorParams将遍历并在this.resolveSingleParam解析它们。可以注意到,如果解析报错了,如果是可选参数则程序会继续运行,这里是因为,this.resolveSingleParam后续的逻辑找不到参数时会抛出错误UnknownDependenciesException

ts 复制代码
public async resolveConstructorParams<T>(/* ... */) {
  // ...
  const [dependencies, optionalDependenciesIds] = isFactoryProvider
    ? this.getFactoryProviderDependencies(wrapper)
    : this.getClassDependencies(wrapper);

  let isResolved = true;
  const resolveParam = async (param: unknown, index: number) => {
    try {
      if (this.isInquirer(param, parentInquirer)) {
        return parentInquirer && parentInquirer.instance;
      }
      if (inquirer?.isTransient && parentInquirer) {
        inquirer = parentInquirer;
        inquirerId = this.getInquirerId(parentInquirer);
      }
      const paramWrapper = await this.resolveSingleParam<T>(
        wrapper,
        param,
        { index, dependencies },
        moduleRef,
        contextId,
        inquirer,
        index,
      );
      const instanceHost = paramWrapper.getInstanceByContextId(
        this.getContextId(contextId, paramWrapper),
        inquirerId,
      );
      if (!instanceHost.isResolved && !paramWrapper.forwardRef) {
        isResolved = false;
      }
      return 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));
}

接下来解析参数的流程是resolveSingleParamresolveComponentInstancelookupComponentresolveComponentHostloadProvider

lookupComponent遍历自身以及imports上的providers找到要注入的参数对应的Provider

这里关键逻辑在resolveComponentInstance,最终递归回到loadProvider中解析ProviderloadProvider又会遍历并解析入参,遍历完每一层入参,最终完成入参的解析,回到resolveComponentHost中的callback

ts 复制代码
public async resolveComponentInstance<T>(
  moduleRef: Module,
  token: InjectionToken,
  dependencyContext: InjectorDependencyContext,
  wrapper: InstanceWrapper<T>,
  contextId = STATIC_CONTEXT,
  inquirer?: InstanceWrapper,
  keyOrIndex?: symbol | string | number,
): Promise<InstanceWrapper> {
  // ...
  const providers = moduleRef.providers;
  const instanceWrapper = await this.lookupComponent(
    providers,
    moduleRef,
    { ...dependencyContext, name: token },
    wrapper,
    contextId,
    inquirer,
    keyOrIndex,
  );

  return this.resolveComponentHost(
    moduleRef,
    instanceWrapper,
    contextId,
    inquirer,
  );
}

到这里用流程图简要总结一下:

graph TB O(createInstances)-->A(loadInstance)-->B(resolveConstructorParams)-->|遍历入参|C(resolveSingleParam)-->D(resolveComponentInstance)-->E(lookupComponent)-->|遍历 imports 寻找Provider|E E-->F(resolveComponentHost)-->G(loadProvider)-->|递归解析 Provider|A C----->|遍历结束实例化依赖|H(callback)

⭕实例化依赖

看回到resolveComponentHost中的callback

ts 复制代码
const callback = async (instances: unknown[]) => {
  const properties = await this.resolveProperties(
    wrapper,
    moduleRef,
    inject as InjectionToken[],
    contextId,
    wrapper,
    inquirer,
  );
  const instance = await this.instantiateClass(
    instances,
    wrapper,
    targetWrapper,
    contextId,
    inquirer,
  );
  this.applyProperties(instance, properties);
  wrapper.initTime = this.getNowTimestamp() - t0;
  settlementSignal.complete();
};

this.tresolveProperties这个函数处理写在类属性中的依赖注入,还是使用上面的resolveSingleParam方法去递归解析、实例化依赖。

ts 复制代码
public async resolveProperties<T>(
  wrapper: InstanceWrapper<T>,
  moduleRef: Module,
  inject?: InjectorDependency[],
  contextId = STATIC_CONTEXT,
  inquirer?: InstanceWrapper,
  parentInquirer?: InstanceWrapper,
): Promise<PropertyDependency[]> {
  // ...
  const properties = this.reflectProperties(wrapper.metatype as Type<any>);
  const instances = await Promise.all(
    properties.map(async (item: PropertyDependency) => {
      try {
        const dependencyContext = {
          key: item.key,
          name: item.name as Function | string | symbol,
        };
        if (this.isInquirer(item.name, parentInquirer)) {
          return parentInquirer && parentInquirer.instance;
        }
        const paramWrapper = await this.resolveSingleParam<T>(
          wrapper,
          item.name,
          dependencyContext,
          moduleRef,
          contextId,
          inquirer,
          item.key,
        );
        if (!paramWrapper) {
          return undefined;
        }
        const inquirerId = this.getInquirerId(inquirer);
        const instanceHost = paramWrapper.getInstanceByContextId(
          this.getContextId(contextId, paramWrapper),
          inquirerId,
        );
        return instanceHost.instance;
      } catch (err) {
        if (!item.isOptional) {
          throw err;
        }
        return undefined;
      }
    }),
  );
  return properties.map((item: PropertyDependency, index: number) => ({
    ...item,
    instance: instances[index],
  }));
}

this.instantiateClass做的事情就是把当前的依赖实例化:

ts 复制代码
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(
    this.getContextId(contextId, targetMetatype),
    inquirerId,
  );
  const isInContext =
    wrapper.isStatic(contextId, inquirer) ||
    wrapper.isInRequestScope(contextId, inquirer) ||
    wrapper.isLazyTransient(contextId, inquirer) ||
    wrapper.isExplicitlyRequested(contextId, inquirer);
  // ...

  if (isNil(inject) && isInContext) {
    // instance 已经创建了原型对象
    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;
}

最后调用this.applyProperties把属性中注入的依赖合并到实例中。

ts 复制代码
public applyProperties<T = any>(
  instance: T,
  properties: PropertyDependency[],
): void {
  if (!isObject(instance)) {
    return undefined;
  }
  iterate(properties)
    .filter(item => !isNil(item.instance))
    .forEach(item => (instance[item.key] = item.instance));
}

在实例化ControllerProvider时候,它们最后还会调用loadEnhancersPerContext,去实例化各种enhancers,也就是上面依赖扫描说到模块上的[INSTANCE_METADATA_SYMBOL]数组。

ts 复制代码
public async loadEnhancersPerContext(
  wrapper: InstanceWrapper,
  ctx: ContextId,
  inquirer?: InstanceWrapper,
) {
  const enhancers = wrapper.getEnhancersMetadata() || [];
  const loadEnhancer = (item: InstanceWrapper) => {
    const hostModule = item.host;
    return this.loadInstance(
      item,
      hostModule.injectables,
      hostModule,
      ctx,
      inquirer,
    );
  };
  await Promise.all(enhancers.map(loadEnhancer));
}

到这里依赖注入大体完成了。

📚结语

这篇博客主要对 Nest 的依赖注入机制的源码进行了简要介绍,这里用流程图简要地总结一下:

graph TB A("启动应用")-->B("创建 IoC 容器")-->O("加入内建模块")-->C("遍历模块并记录到容器")-->D("扫描模块各组件并记录到容器")-->E("遍历并实例化各组件原型")-->F("遍历并实例化各组件")

大家的阅读是我发帖的动力。

本文首发于我的博客:deerblog.gu-nami.com/

相关推荐
亮子AI16 天前
【NestJS】为什么return不返回客户端?
前端·javascript·git·nestjs
小p16 天前
nestjs学习2:利用typescript改写express服务
nestjs
Eric_见嘉22 天前
NestJS 🧑‍🍳 厨子必修课(九):API 文档 Swagger
前端·后端·nestjs
XiaoYu20021 个月前
第3章 Nest.js拦截器
前端·ai编程·nestjs
XiaoYu20021 个月前
第2章 Nest.js入门
前端·ai编程·nestjs
实习生小黄1 个月前
NestJS 调试方案
后端·nestjs
当时只道寻常1 个月前
NestJS 如何配置环境变量
nestjs
濮水大叔2 个月前
VonaJS是如何做到文件级别精确HMR(热更新)的?
typescript·node.js·nestjs
ovensi2 个月前
告别笨重的 ELK,拥抱轻量级 PLG:NestJS 日志监控实战指南
nestjs