coverPiccoverPic

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
  1. @Module({
  2. imports: [
  3. SequelizeModule.forFeature([
  4. ArticleModel,
  5. ]),
  6. ServerLogger
  7. ],
  8. controllers: [ArticleController],
  9. providers: [ArticleService]
  10. })
  11. export class ArticleModule {}

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

ts
  1. @Injectable()
  2. export class ArticleService {
  3. constructor (
  4. @InjectModel(ArticleModel) private readonly articleModel: typeof ArticleModel,
  5. @Inject(ServerLoggerService) private readonly logger: ServerLoggerService,
  6. ) {}
  7. test() {
  8. console.log(this.logger instanceof ServerLoggerService);
  9. console.log(this.articleModel);
  10. }
  11. // ...
  12. }

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

ts
  1. @Controller('article')
  2. export class ArticleController {
  3. constructor (
  4. private readonly articleService: ArticleService
  5. ) {}
  6. @Get('test')
  7. test() {
  8. console.log(this.articleService instanceof ArticleService);
  9. return this.articleService.test();
  10. }
  11. // ...
  12. }

我们来访问一下 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
  1. @Module({
  2. imports: [
  3. SequelizeModule.forFeature([
  4. ArticleModel,
  5. ]),
  6. ServerLogger
  7. ],
  8. controllers: [ArticleController],
  9. providers: [ArticleService]
  10. })
  11. export class ArticleModule {}

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

ts
  1. export function Module(metadata: ModuleMetadata): ClassDecorator {
  2. const propsKeys = Object.keys(metadata);
  3. validateModuleKeys(propsKeys);
  4. return (target: Function) => {
  5. for (const property in metadata) {
  6. if (metadata.hasOwnProperty(property)) {
  7. Reflect.defineMetadata(property, (metadata as any)[property], target);
  8. }
  9. }
  10. };
  11. }

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

🧰依赖注入

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

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

ts
  1. export function Inject<T = any>(
  2. token?: T,
  3. ): PropertyDecorator & ParameterDecorator {
  4. return (target: object, key: string | symbol | undefined, index?: number) => {
  5. const type = token || Reflect.getMetadata('design:type', target, key);
  6. if (!isUndefined(index)) {
  7. let dependencies =
  8. Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, target) || [];
  9. dependencies = [...dependencies, { index, param: type }];
  10. Reflect.defineMetadata(SELF_DECLARED_DEPS_METADATA, dependencies, target);
  11. return;
  12. }
  13. let properties =
  14. Reflect.getMetadata(PROPERTY_DEPS_METADATA, target.constructor) || [];
  15. properties = [...properties, { key, type }];
  16. Reflect.defineMetadata(
  17. PROPERTY_DEPS_METADATA,
  18. properties,
  19. target.constructor,
  20. );
  21. };
  22. }

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

ts
  1. export function Optional(): PropertyDecorator & ParameterDecorator {
  2. return (target: object, key: string | symbol | undefined, index?: number) => {
  3. if (!isUndefined(index)) {
  4. const args = Reflect.getMetadata(OPTIONAL_DEPS_METADATA, target) || [];
  5. Reflect.defineMetadata(OPTIONAL_DEPS_METADATA, [...args, index], target);
  6. return;
  7. }
  8. const properties =
  9. Reflect.getMetadata(
  10. OPTIONAL_PROPERTY_DEPS_METADATA,
  11. target.constructor,
  12. ) || [];
  13. Reflect.defineMetadata(
  14. OPTIONAL_PROPERTY_DEPS_METADATA,
  15. [...properties, key],
  16. target.constructor,
  17. );
  18. };
  19. }

📌入口逻辑

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

ts
  1. async function bootstrap() {
  2. const app = await NestFactory.create(AppModule);
  3. app.useGlobalPipes(new ValidationPipe());
  4. await app.listen(3000);
  5. console.log(`Application is running on: ${await app.getUrl()}`);
  6. }
  7. bootstrap();

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

ts
  1. public async create<T extends INestApplication = INestApplication>(
  2. moduleCls: any,
  3. serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
  4. options?: NestApplicationOptions,
  5. ): Promise<T> {
  6. const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
  7. ? [serverOrOptions, options]
  8. : [this.createHttpAdapter(), serverOrOptions];
  9. const applicationConfig = new ApplicationConfig();
  10. // 创建 IoC 容器,用于记录和管理模块及其依赖
  11. const container = new NestContainer(applicationConfig);
  12. const graphInspector = this.createGraphInspector(appOptions, container);
  13. this.setAbortOnError(serverOrOptions, options);
  14. this.registerLoggerConfiguration(appOptions);
  15. // 初始化,进行扫描和注入依赖,实例化类等逻辑
  16. await this.initialize(
  17. moduleCls,
  18. container,
  19. graphInspector,
  20. applicationConfig,
  21. appOptions,
  22. httpServer,
  23. );
  24. // 创建 Nest 应用
  25. const instance = new NestApplication(
  26. container,
  27. httpServer,
  28. applicationConfig,
  29. graphInspector,
  30. appOptions,
  31. );
  32. const target = this.createNestInstance(instance);
  33. return this.createAdapterProxy<T>(target, httpServer);
  34. }

主要来看this.initialize的逻辑:

ts
  1. private async initialize(
  2. module: any,
  3. container: NestContainer,
  4. graphInspector: GraphInspector,
  5. config = new ApplicationConfig(),
  6. options: NestApplicationContextOptions = {},
  7. httpServer: HttpServer = null,
  8. ) {
  9. UuidFactory.mode = options.snapshot
  10. ? UuidFactoryMode.Deterministic
  11. : UuidFactoryMode.Random;
  12. // 创建注入器
  13. const injector = new Injector({ preview: options.preview });
  14. // 创建实例加载器,用于实例化类
  15. const instanceLoader = new InstanceLoader(
  16. container,
  17. injector,
  18. graphInspector,
  19. );
  20. const metadataScanner = new MetadataScanner();
  21. // 创建依赖扫描器,用于获取模块及其依赖
  22. const dependenciesScanner = new DependenciesScanner(
  23. container,
  24. metadataScanner,
  25. graphInspector,
  26. config,
  27. );
  28. container.setHttpAdapter(httpServer);
  29. const teardown = this.abortOnError === false ? rethrow : undefined;
  30. await httpServer?.init();
  31. try {
  32. this.logger.log(MESSAGES.APPLICATION_START);
  33. await ExceptionsZone.asyncRun(
  34. async () => {
  35. // 依赖扫描
  36. await dependenciesScanner.scan(module);
  37. // 依赖注入和模块实例化
  38. await instanceLoader.createInstancesOfDependencies();
  39. // 扫描 App 中的 Provider
  40. dependenciesScanner.applyApplicationProviders();
  41. },
  42. teardown,
  43. this.autoFlushLogs,
  44. );
  45. } catch (e) {
  46. this.handleInitializationError(e);
  47. }
  48. }

总之,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
  1. public async scan(
  2. module: Type<any>,
  3. options?: { overrides?: ModuleOverride[] },
  4. ) {
  5. // 注册内建模块
  6. await this.registerCoreModule(options?.overrides);
  7. // 扫描并在 IoC 容器中添加模块
  8. await this.scanForModules({
  9. moduleDefinition: module,
  10. overrides: options?.overrides,
  11. });
  12. // 处理模块的 imports、 providers、controlles、exports,并记录在 IoC 容器中
  13. await this.scanModulesForDependencies();
  14. this.calculateModulesDistance();
  15. this.addScopedEnhancersMetadata();
  16. // 把全局模块和内建模块加入到 imports 中
  17. this.container.bindGlobalScope();
  18. }

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

📑记录模块

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

ts
  1. public async scanForModules({
  2. moduleDefinition,
  3. lazy,
  4. scope = [],
  5. ctxRegistry = [],
  6. overrides = [],
  7. }: ModulesScanParameters): Promise<Module[]> {
  8. const { moduleRef: moduleInstance, inserted: moduleInserted } =
  9. // 添加模块
  10. (await this.insertOrOverrideModule(moduleDefinition, overrides, scope)) ??
  11. {};
  12. // ...
  13. let registeredModuleRefs = [];
  14. for (const [index, innerModule] of modules.entries()) {
  15. if (ctxRegistry.includes(innerModule)) {
  16. continue;
  17. }
  18. // 深度遍历
  19. const moduleRefs = await this.scanForModules({
  20. moduleDefinition: innerModule,
  21. scope: [].concat(scope, moduleDefinition),
  22. ctxRegistry,
  23. overrides,
  24. lazy,
  25. });
  26. registeredModuleRefs = registeredModuleRefs.concat(moduleRefs);
  27. }
  28. // ...
  29. return [moduleInstance].concat(registeredModuleRefs);
  30. }

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

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

ts
  1. public async addModule(
  2. metatype: ModuleMetatype,
  3. scope: ModuleScope,
  4. ): Promise<{ moduleRef: Module; inserted: boolean; } | undefined> {
  5. const { type, dynamicMetadata, token } =
  6. await this.moduleCompiler.compile(metatype);
  7. if (this.modules.has(token)) {
  8. return { moduleRef: this.modules.get(token), inserted: true };
  9. }
  10. return {
  11. moduleRef: await this.setModule({ token, type, dynamicMetadata }, scope),
  12. inserted: true,
  13. };
  14. }
  15. private async setModule(
  16. { token, dynamicMetadata, type }: ModuleFactory, scope: ModuleScope,
  17. ): Promise<Module | undefined> {
  18. const moduleRef = new Module(type, this);
  19. moduleRef.token = token;
  20. moduleRef.initOnPreview = this.shouldInitOnPreview(type);
  21. this.modules.set(token, moduleRef);
  22. const updatedScope = [].concat(scope, type);
  23. await this.addDynamicMetadata(token, dynamicMetadata, updatedScope);
  24. if (this.isGlobalModule(type, dynamicMetadata)) {
  25. moduleRef.isGlobal = true;
  26. this.addGlobalModule(moduleRef);
  27. }
  28. return moduleRef;
  29. }

执行完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
  1. public async scanModulesForDependencies(
  2. modules: Map<string, Module> = this.container.getModules(),
  3. ) {
  4. for (const [token, { metatype }] of modules) {
  5. await this.reflectImports(metatype, token, metatype.name);
  6. this.reflectProviders(metatype, token);
  7. this.reflectControllers(metatype, token);
  8. this.reflectExports(metatype, token);
  9. }
  10. }

这里逻辑比较繁琐,就不细说了,大概是遍历各个字段,记录所有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
  1. get providers(): Map<InjectionToken, InstanceWrapper<Injectable>> {
  2. return this._providers;
  3. }
  4. get imports(): Set<Module> {
  5. return this._imports;
  6. }
  7. get injectables(): Map<InjectionToken, InstanceWrapper<Injectable>> {
  8. return this._injectables;
  9. }
  10. get controllers(): Map<InjectionToken, InstanceWrapper<Controller>> {
  11. return this._controllers;
  12. }
  13. get exports(): Set<InjectionToken> {
  14. return this._exports;
  15. }

💉依赖注入

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

ts
  1. public async createInstancesOfDependencies(
  2. modules: Map<string, Module> = this.container.getModules(),
  3. ) {
  4. // 实例化原型
  5. this.createPrototypes(modules);
  6. try {
  7. // 实例化依赖
  8. await this.createInstances(modules);
  9. } catch (err) {
  10. this.graphInspector.inspectModules(modules);
  11. this.graphInspector.registerPartial(err);
  12. throw err;
  13. }
  14. this.graphInspector.inspectModules(modules);
  15. }

🧬实例化原型对象

createPrototypes依次实例化providersinjectablescontrollers的原型

ts
  1. private createPrototypes(modules: Map<string, Module>) {
  2. modules.forEach(moduleRef => {
  3. this.createPrototypesOfProviders(moduleRef);
  4. this.createPrototypesOfInjectables(moduleRef);
  5. this.createPrototypesOfControllers(moduleRef);
  6. });
  7. }
  8. private createPrototypesOfProviders(moduleRef: Module) {
  9. const { providers } = moduleRef;
  10. providers.forEach(wrapper =>
  11. this.injector.loadPrototype<Injectable>(wrapper, providers),
  12. );
  13. }
  14. private createPrototypesOfInjectables(moduleRef: Module) {
  15. const { injectables } = moduleRef;
  16. injectables.forEach(wrapper =>
  17. this.injector.loadPrototype(wrapper, injectables),
  18. );
  19. }
  20. private createPrototypesOfControllers(moduleRef: Module) {
  21. const { controllers } = moduleRef;
  22. controllers.forEach(wrapper =>
  23. this.injector.loadPrototype<Controller>(wrapper, controllers),
  24. );
  25. }

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

ts
  1. public loadPrototype<T>(
  2. { token }: InstanceWrapper<T>,
  3. collection: Map<InjectionToken, InstanceWrapper<T>>,
  4. contextId = STATIC_CONTEXT,
  5. ) {
  6. if (!collection) {
  7. return;
  8. }
  9. const target = collection.get(token);
  10. const instance = target.createPrototype(contextId);
  11. if (instance) {
  12. const wrapper = new InstanceWrapper({
  13. ...target,
  14. instance,
  15. });
  16. collection.set(token, wrapper);
  17. }
  18. }
  19. public createPrototype(contextId: ContextId) {
  20. const host = this.getInstanceByContextId(contextId);
  21. if (!this.isNewable() || host.isResolved) {
  22. return;
  23. }
  24. return Object.create(this.metatype.prototype);
  25. }

总结一下这里的流程:

graph LR
A(createPrototypes) -->|①| createPrototypesOfProviders -->B(loadPrototype) -->createPrototype
A -->|②| createPrototypesOfInjectables --> B
A -->|③| createPrototypesOfControllers --> B

🧫实例化依赖入参

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

ts
  1. private async createInstances(modules: Map<string, Module>) {
  2. await Promise.all(
  3. [...modules.values()].map(async moduleRef => {
  4. await this.createInstancesOfProviders(moduleRef);
  5. await this.createInstancesOfInjectables(moduleRef);
  6. await this.createInstancesOfControllers(moduleRef);
  7. const { name } = moduleRef;
  8. this.isModuleWhitelisted(name) &&
  9. this.logger.log(MODULE_INIT_MESSAGE`${name}`);
  10. }),
  11. );
  12. }

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

ts
  1. private async createInstancesOfProviders(moduleRef: Module) {
  2. const { providers } = moduleRef;
  3. const wrappers = [...providers.values()];
  4. await Promise.all(
  5. wrappers.map(async item => {
  6. await this.injector.loadProvider(item, moduleRef);
  7. this.graphInspector.inspectInstanceWrapper(item, moduleRef);
  8. }),
  9. );
  10. }
  11. // ...
  12. // packages/core/injector/injector.ts
  13. public async loadProvider(
  14. wrapper: InstanceWrapper<Injectable>,
  15. moduleRef: Module,
  16. contextId = STATIC_CONTEXT,
  17. inquirer?: InstanceWrapper,
  18. ) {
  19. const providers = moduleRef.providers;
  20. await this.loadInstance<Injectable>(
  21. wrapper,
  22. providers,
  23. moduleRef,
  24. contextId,
  25. inquirer,
  26. );
  27. await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
  28. }
  29. // ...

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

ts
  1. public async loadInstance<T>(
  2. wrapper: InstanceWrapper<T>,
  3. collection: Map<InjectionToken, InstanceWrapper>,
  4. moduleRef: Module,
  5. contextId = STATIC_CONTEXT,
  6. inquirer?: InstanceWrapper,
  7. ) {
  8. // ...
  9. try {
  10. const t0 = this.getNowTimestamp();
  11. const callback = async (instances: unknown[]) => {
  12. // 完成实例化,下面再说
  13. };
  14. // 解析构造函数参数
  15. await this.resolveConstructorParams<T>(
  16. wrapper,
  17. moduleRef,
  18. inject as InjectionToken[],
  19. callback,
  20. contextId,
  21. wrapper,
  22. inquirer,
  23. );
  24. } catch (err) {
  25. settlementSignal.error(err);
  26. throw err;
  27. }
  28. }

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

FactoryProviderthis.getFactoryProviderDependenciesclass形式的则走this.getClassDependencies

ts
  1. public async resolveConstructorParams<T>(
  2. wrapper: InstanceWrapper<T>,
  3. moduleRef: Module,
  4. inject: InjectorDependency[],
  5. callback: (args: unknown[]) => void | Promise<void>,
  6. contextId = STATIC_CONTEXT,
  7. inquirer?: InstanceWrapper,
  8. parentInquirer?: InstanceWrapper,
  9. ) {
  10. // ...
  11. const isFactoryProvider = !isNil(inject);
  12. const [dependencies, optionalDependenciesIds] = isFactoryProvider
  13. ? this.getFactoryProviderDependencies(wrapper)
  14. : this.getClassDependencies(wrapper);
  15. // ...
  16. }

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

ts
  1. providers: [ArticleService] // ArticleService 是一个 class

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

ts
  1. providers: [
  2. {
  3. provide: 'ArticleService',
  4. useFactory: (aService: AService, tempService?: TempService) => {
  5. return new SomeProvider(aService, tempService);
  6. },
  7. inject: [AService, { token: TempService, optional: true }]
  8. },
  9. ];

它的入参就是inject数组。

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

ts
  1. public getFactoryProviderDependencies<T>(
  2. wrapper: InstanceWrapper<T>,
  3. ): [InjectorDependency[], number[]] {
  4. const optionalDependenciesIds = [];
  5. // ...
  6. const mapFactoryProviderInjectArray = (
  7. item: InjectionToken | OptionalFactoryDependency,
  8. index: number,
  9. ): InjectionToken => {
  10. if (typeof item !== 'object') {
  11. return item;
  12. }
  13. if (isOptionalFactoryDep(item)) {
  14. if (item.optional) {
  15. optionalDependenciesIds.push(index);
  16. }
  17. return item?.token;
  18. }
  19. return item;
  20. };
  21. return [
  22. wrapper.inject?.map?.(mapFactoryProviderInjectArray),
  23. optionalDependenciesIds,
  24. ];
  25. }

对于通常的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
  1. public getClassDependencies<T>(
  2. wrapper: InstanceWrapper<T>,
  3. ): [InjectorDependency[], number[]] {
  4. const ctorRef = wrapper.metatype as Type<any>;
  5. return [
  6. this.reflectConstructorParams(ctorRef),
  7. this.reflectOptionalParams(ctorRef),
  8. ];
  9. }
  10. public reflectConstructorParams<T>(type: Type<T>): any[] {
  11. const paramtypes = [
  12. ...(Reflect.getMetadata(PARAMTYPES_METADATA, type) || []),
  13. ];
  14. const selfParams = this.reflectSelfParams<T>(type);
  15. selfParams.forEach(({ index, param }) => (paramtypes[index] = param));
  16. return paramtypes;
  17. }
  18. public reflectOptionalParams<T>(type: Type<T>): any[] {
  19. return Reflect.getMetadata(OPTIONAL_DEPS_METADATA, type) || [];
  20. }
  21. public reflectSelfParams<T>(type: Type<T>): any[] {
  22. return Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || [];
  23. }

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

ts
  1. public async resolveConstructorParams<T>(/* ... */) {
  2. // ...
  3. const [dependencies, optionalDependenciesIds] = isFactoryProvider
  4. ? this.getFactoryProviderDependencies(wrapper)
  5. : this.getClassDependencies(wrapper);
  6. let isResolved = true;
  7. const resolveParam = async (param: unknown, index: number) => {
  8. try {
  9. if (this.isInquirer(param, parentInquirer)) {
  10. return parentInquirer && parentInquirer.instance;
  11. }
  12. if (inquirer?.isTransient && parentInquirer) {
  13. inquirer = parentInquirer;
  14. inquirerId = this.getInquirerId(parentInquirer);
  15. }
  16. const paramWrapper = await this.resolveSingleParam<T>(
  17. wrapper,
  18. param,
  19. { index, dependencies },
  20. moduleRef,
  21. contextId,
  22. inquirer,
  23. index,
  24. );
  25. const instanceHost = paramWrapper.getInstanceByContextId(
  26. this.getContextId(contextId, paramWrapper),
  27. inquirerId,
  28. );
  29. if (!instanceHost.isResolved && !paramWrapper.forwardRef) {
  30. isResolved = false;
  31. }
  32. return instanceHost?.instance;
  33. } catch (err) {
  34. const isOptional = optionalDependenciesIds.includes(index);
  35. if (!isOptional) {
  36. throw err;
  37. }
  38. return undefined;
  39. }
  40. };
  41. const instances = await Promise.all(dependencies.map(resolveParam));
  42. isResolved && (await callback(instances));
  43. }

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

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

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

ts
  1. public async resolveComponentInstance<T>(
  2. moduleRef: Module,
  3. token: InjectionToken,
  4. dependencyContext: InjectorDependencyContext,
  5. wrapper: InstanceWrapper<T>,
  6. contextId = STATIC_CONTEXT,
  7. inquirer?: InstanceWrapper,
  8. keyOrIndex?: symbol | string | number,
  9. ): Promise<InstanceWrapper> {
  10. // ...
  11. const providers = moduleRef.providers;
  12. const instanceWrapper = await this.lookupComponent(
  13. providers,
  14. moduleRef,
  15. { ...dependencyContext, name: token },
  16. wrapper,
  17. contextId,
  18. inquirer,
  19. keyOrIndex,
  20. );
  21. return this.resolveComponentHost(
  22. moduleRef,
  23. instanceWrapper,
  24. contextId,
  25. inquirer,
  26. );
  27. }

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

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
  1. const callback = async (instances: unknown[]) => {
  2. const properties = await this.resolveProperties(
  3. wrapper,
  4. moduleRef,
  5. inject as InjectionToken[],
  6. contextId,
  7. wrapper,
  8. inquirer,
  9. );
  10. const instance = await this.instantiateClass(
  11. instances,
  12. wrapper,
  13. targetWrapper,
  14. contextId,
  15. inquirer,
  16. );
  17. this.applyProperties(instance, properties);
  18. wrapper.initTime = this.getNowTimestamp() - t0;
  19. settlementSignal.complete();
  20. };

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

ts
  1. public async resolveProperties<T>(
  2. wrapper: InstanceWrapper<T>,
  3. moduleRef: Module,
  4. inject?: InjectorDependency[],
  5. contextId = STATIC_CONTEXT,
  6. inquirer?: InstanceWrapper,
  7. parentInquirer?: InstanceWrapper,
  8. ): Promise<PropertyDependency[]> {
  9. // ...
  10. const properties = this.reflectProperties(wrapper.metatype as Type<any>);
  11. const instances = await Promise.all(
  12. properties.map(async (item: PropertyDependency) => {
  13. try {
  14. const dependencyContext = {
  15. key: item.key,
  16. name: item.name as Function | string | symbol,
  17. };
  18. if (this.isInquirer(item.name, parentInquirer)) {
  19. return parentInquirer && parentInquirer.instance;
  20. }
  21. const paramWrapper = await this.resolveSingleParam<T>(
  22. wrapper,
  23. item.name,
  24. dependencyContext,
  25. moduleRef,
  26. contextId,
  27. inquirer,
  28. item.key,
  29. );
  30. if (!paramWrapper) {
  31. return undefined;
  32. }
  33. const inquirerId = this.getInquirerId(inquirer);
  34. const instanceHost = paramWrapper.getInstanceByContextId(
  35. this.getContextId(contextId, paramWrapper),
  36. inquirerId,
  37. );
  38. return instanceHost.instance;
  39. } catch (err) {
  40. if (!item.isOptional) {
  41. throw err;
  42. }
  43. return undefined;
  44. }
  45. }),
  46. );
  47. return properties.map((item: PropertyDependency, index: number) => ({
  48. ...item,
  49. instance: instances[index],
  50. }));
  51. }

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

ts
  1. public async instantiateClass<T = any>(
  2. instances: any[],
  3. wrapper: InstanceWrapper,
  4. targetMetatype: InstanceWrapper,
  5. contextId = STATIC_CONTEXT,
  6. inquirer?: InstanceWrapper,
  7. ): Promise<T> {
  8. const { metatype, inject } = wrapper;
  9. const inquirerId = this.getInquirerId(inquirer);
  10. const instanceHost = targetMetatype.getInstanceByContextId(
  11. this.getContextId(contextId, targetMetatype),
  12. inquirerId,
  13. );
  14. const isInContext =
  15. wrapper.isStatic(contextId, inquirer) ||
  16. wrapper.isInRequestScope(contextId, inquirer) ||
  17. wrapper.isLazyTransient(contextId, inquirer) ||
  18. wrapper.isExplicitlyRequested(contextId, inquirer);
  19. // ...
  20. if (isNil(inject) && isInContext) {
  21. // instance 已经创建了原型对象
  22. instanceHost.instance = wrapper.forwardRef
  23. ? Object.assign(
  24. instanceHost.instance,
  25. new (metatype as Type<any>)(...instances),
  26. )
  27. : new (metatype as Type<any>)(...instances);
  28. } else if (isInContext) {
  29. const factoryReturnValue = (targetMetatype.metatype as any as Function)(
  30. ...instances,
  31. );
  32. instanceHost.instance = await factoryReturnValue;
  33. }
  34. instanceHost.isResolved = true;
  35. return instanceHost.instance;
  36. }

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

ts
  1. public applyProperties<T = any>(
  2. instance: T,
  3. properties: PropertyDependency[],
  4. ): void {
  5. if (!isObject(instance)) {
  6. return undefined;
  7. }
  8. iterate(properties)
  9. .filter(item => !isNil(item.instance))
  10. .forEach(item => (instance[item.key] = item.instance));
  11. }

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

ts
  1. public async loadEnhancersPerContext(
  2. wrapper: InstanceWrapper,
  3. ctx: ContextId,
  4. inquirer?: InstanceWrapper,
  5. ) {
  6. const enhancers = wrapper.getEnhancersMetadata() || [];
  7. const loadEnhancer = (item: InstanceWrapper) => {
  8. const hostModule = item.host;
  9. return this.loadInstance(
  10. item,
  11. hostModule.injectables,
  12. hostModule,
  13. ctx,
  14. inquirer,
  15. );
  16. };
  17. await Promise.all(enhancers.map(loadEnhancer));
  18. }

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

📚结语

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

graph TB
A("启动应用")-->B("创建 IoC 容器")-->O("加入内建模块")-->C("遍历模块并记录到容器")-->D("扫描模块各组件并记录到容器")-->E("遍历并实例化各组件原型")-->F("遍历并实例化各组件")
0 条评论未登录用户
Ctrl or + Enter 评论
🌸 Run