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/

相关推荐
_jiang2 天前
nestjs 入门实战最强篇
redis·typescript·nestjs
敲代码的彭于晏4 天前
【Nest.js 10】JWT+Redis实现登录互踢
前端·后端·nestjs
前端小王hs18 天前
Nest通用工具函数执行顺序
javascript·后端·nestjs
明远湖之鱼20 天前
从入门到入门学习NestJS
前端·后端·nestjs
吃葡萄不吐番茄皮22 天前
从零开始学 NestJS(一):为什么要学习 Nest
前端·nestjs
东方小月23 天前
Vue3+NestJS实现权限管理系统(六):接口按钮权限控制
前端·后端·nestjs
白雾茫茫丶25 天前
Nest.js 实战 (十四):如何获取客户端真实 IP
nginx·nestjs
Spirited_Away1 个月前
Nest世界中的AOP
前端·node.js·nestjs
Eric_见嘉1 个月前
NestJS 🧑‍🍳 厨子必修课(六):Prisma 集成(下)
前端·后端·nestjs
kongxx2 个月前
NestJS中使用Guard实现路由保护
nestjs