欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Nest.js 之依賴注入原理及實現(xiàn)過程詳解

 更新時間:2023年01月12日 14:10:48   作者:Aaaaaaaaaaayou  
這篇文章主要為大家介紹了Nest.js 之依賴注入原理及實現(xiàn)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

很久之前初學 Java 時就對注解及自動依賴注入這種方式感覺到不可思議,但是一直沒有勇氣(懶)去搞清楚。現(xiàn)在做前端了,發(fā)現(xiàn) Nest.js 里面竟然也是這玩意,終究還是躲不過,那就趁著“陽康”了搞清楚一下吧。

關(guān)于為什么要進行依賴注入這里就不展開了,下面直接進入正題,TypeScript 依賴注入的原理。

TypeScript 依賴注入的原理

TypeScript 中實現(xiàn)依賴注入離不開 DecoratorMetadata(需要引入第三方庫 reflect-metadata),下面通過一個簡單的例子來快速了解它的用途:

import 'reflect-metadata'
@Reflect.metadata('class', 'Class Data')
class Test {
  @Reflect.metadata('method', 'Method Data')
  public hello(): string {
    return 'hello world'
  }
}
console.log(Reflect.getMetadata('class', Test)) // Class Data
console.log(Reflect.getMetadata('method', new Test(), 'hello')) // Method Data

通過例子可以看到,我們通過 Reflect.metadata() 這個裝飾器可以往類及其方法上面添加數(shù)據(jù),然后通過 Reflect.getMetadata 可以取到這些數(shù)據(jù)。我們可以借助這一特性,實現(xiàn)簡單的依賴注入:

import 'reflect-metadata'
class TestService {}
@Reflect.metadata('params', [TestService])
class Test {
  constructor(testService: TestService) {}
  public hello(): string {
    return 'hello world'
  }
}
type Constructor<T = any> = new (...args: any[]) => T
const inject = <T>(target: Constructor<T>): T => {
  const providers = Reflect.getMetadata('params', target)
  const args = providers.map((provider: Constructor) => new provider())
  return new target(...args)
}
inject(Test).hello()

如上所示,我們通過 @Reflect.metadata('params', [TestService])Test 上添加了元數(shù)據(jù),表示構(gòu)造函數(shù)中需要用到 TestService,但 Nest.js 中好像不需要這樣。怎么辦呢?答案就是:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true
  }
}

開啟了這個參數(shù)后,我們就不需要手動添加元數(shù)據(jù)了:

import 'reflect-metadata'
class TestService {}
const D = (): ClassDecorator => (target) => {}
@D()
class Test {
  constructor(testService: TestService) {}
  public hello(): string {
    return 'hello world'
  }
}
type Constructor<T = any> = new (...args: any[]) => T
const inject = <T>(target: Constructor<T>): T => {
  const providers = Reflect.getMetadata('design:paramtypes', target)
  const args = providers.map((provider: Constructor) => new provider())
  return new target(...args)
}
inject(Test).hello()

原因在于開啟 emitDecoratorMetadata 后,TS 自動會在我們的裝飾器前添加一些裝飾器。比如,下面這段代碼:

import 'reflect-metadata'
const D = (): ClassDecorator => (target) => {}
const methodDecorator = (): MethodDecorator => (target, key, descriptor) => {}
@D()
class Test {
  constructor(a: number) {}
  @methodDecorator()
  public hello(): string {
    return 'hello world'
  }
  public hi() {}
}

編譯過后是這樣子的:

var __decorate = ...
var __metadata = ...
import 'reflect-metadata'
const D = () => (target) => {}
const methodDecorator = () => (target, key, descriptor) => {}
let Test = class Test {
  constructor(a) {}
  hello() {
    return 'hello world'
  }
  hi() {}
}
__decorate(
  [
    methodDecorator(),
    __metadata('design:type', Function),
    __metadata('design:paramtypes', []),
    __metadata('design:returntype', String),
  ],
  Test.prototype,
  'hello',
  null
)
Test = __decorate(
  [D(), __metadata('design:paramtypes', [Number])],
  Test
)

可以看到,TS 自動會添加 design:type|paramtypes|returntype 三種類型的元數(shù)據(jù),分別表示目標本身,參數(shù)以及返回值的類型。

我們把 inject 稍微改一下,支持遞歸的注入,這樣一個簡單的依賴注入就實現(xiàn)了:

import 'reflect-metadata'
const D = (): ClassDecorator => (target) => {}
class OtherService {}
@D()
class TestService {
  constructor(otherService: OtherService) {}
}
@D()
class Test {
  constructor(testService: TestService) {}
  public hello(): string {
    return 'hello world'
  }
}
type Constructor<T = any> = new (...args: any[]) => T
const inject = <T>(target: Constructor<T>): T => {
  const providers = Reflect.getMetadata('design:paramtypes', target)
  if (providers) {
    const args = providers.map((provider: Constructor) => {
      return inject(provider)
    })
    return new target(...args)
  }
  return new target()
}
inject(Test).hello()

接下來,我們淺看一下 Nest.js 大概是怎么實現(xiàn)的。

淺析 Nest.js 實現(xiàn)依賴注入的過程

我們通過官方腳手架生成一個 Demo 項目,可以發(fā)現(xiàn)其中 tsconfig.json 中的 emitDecoratorMetadata 確實是開啟的。我們先用一個最簡單的例子來說明:

// app.module.ts
import {Injectable, Module} from '@nestjs/common'
@Injectable()
class TestService {
  hello() {
    return 'hello world'
  }
}
@Module({
  providers: [TestService],
})
export class AppModule {
  constructor(testService: TestService) {
    testService.hello()
  }
}
// main.ts
import {NestFactory} from '@nestjs/core'
import {AppModule} from './app.module'
async function bootstrap() {
  await NestFactory.create(AppModule)
}
bootstrap()

為了更加直觀的理解流程,這里暫時先把源碼核心部分扒下來,我們把 await NestFactory.create(AppModule) 替換成我們自己的代碼:

const injector = new Injector()
await injector.inject(AppModule)
import {Type} from '@nestjs/common'
import {MODULE_METADATA} from '@nestjs/common/constants'
import {ApplicationConfig, NestContainer} from '@nestjs/core'
import {InstanceLoader} from '@nestjs/core/injector/instance-loader'
export default class Injector {
  container: NestContainer
  public async inject(module: any) {
    const applicationConfig = new ApplicationConfig()
    this.container = new NestContainer(applicationConfig)
    const moduleInstance = await this.container.addModule(module, null)
    // 1 resolve dependencies
    const {token, metatype} = moduleInstance
    this.reflectProviders(metatype, token)
    // 2 create instance
    const instanceLoader = new InstanceLoader(this.container)
    instanceLoader.createInstancesOfDependencies()
  }
  public reflectProviders(module: Type<any>, token: string) {
    const providers = [
      ...this.reflectMetadata(MODULE_METADATA.PROVIDERS, module),
    ]
    providers.forEach((provider) => {
      return this.container.addProvider(provider as Type<any>, token)
    })
  }
  public reflectMetadata(metadataKey: string, metatype: Type<any>) {
    return Reflect.getMetadata(metadataKey, metatype) || []
  }
}

這里大概分成兩部分:

  • 處理 module 的依賴,也就是 @Module 裝飾器所聲明的,我們這里暫時只考慮 providers。這一步執(zhí)行完后,NestContainer 中數(shù)據(jù)如下(注意到 Module 本身也作為自己的 provider):
{
  modules: {
    '19bb8f429cacdbcc18fc1afcaac891a4606578aa': Module {
      _metatype: class AppModule {...},
      _providers: {
        class AppModule {...}: InstanceWrapper {}, // Module 本身也作為自己的 provider
        class TestService {...}: InstanceWrapper {}
      }
    }
  }
}
  • 實例化 Module。這一部分需要稍微看一下源碼:
public async createInstancesOfDependencies(
  modules: Map<string, Module> = this.container.getModules(),
) {
  ...
  await this.createInstances(modules);
}
...
private async createInstances(modules: Map<string, Module>) {
  await Promise.all(
    [...modules.values()].map(async moduleRef => {
      await this.createInstancesOfProviders(moduleRef);
      ...
    }),
  );
}

這里的意思是實例化所有的 Module,實例化 Module 前,我們需要先實例化它的依賴,具體到這里就是實例化 providers

private async createInstancesOfProviders(moduleRef: Module) {
  const { providers } = moduleRef;
  const wrappers = [...providers.values()];
  await Promise.all(
    wrappers.map(item => this.injector.loadProvider(item, moduleRef)),
  );
}

最后會到 loadInstance 這個函數(shù):

  public async loadInstance<T>(
    wrapper: InstanceWrapper<T>,
    collection: Map<InstanceToken, InstanceWrapper>,
    moduleRef: Module,
    contextId = STATIC_CONTEXT,
    inquirer?: InstanceWrapper,
  ) {
    ...
    try {
      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);
        done();
      };
      await this.resolveConstructorParams<T>(
        wrapper,
        moduleRef,
        inject as InjectionToken[],
        callback,
        contextId,
        wrapper,
        inquirer,
      );
    } catch (err) {
      done(err);
      throw err;
    }
  }

接下來就到了最重要的 this.resolveConstructorParams 這個函數(shù)了,我們以 class AppModule 這個 provider 為例來分析:

public async resolveConstructorParams<T>(
    wrapper: InstanceWrapper<T>,
    moduleRef: Module,
    inject: InjectorDependency[],
    callback: (args: unknown[]) => void | Promise<void>,
    contextId = STATIC_CONTEXT,
    inquirer?: InstanceWrapper,
    parentInquirer?: InstanceWrapper,
  ) {
    // dependencies 返回的就是 AppModule 構(gòu)造函數(shù)的參數(shù)類型,本例為: [TestService]
    const [dependencies, optionalDependenciesIds] = isFactoryProvider
      ? this.getFactoryProviderDependencies(wrapper)
      : this.getClassDependencies(wrapper);
    let isResolved = true;
    const resolveParam = async (param: unknown, index: number) => {
      ...
    };
    // 這里的 instances 就是通過 dependencies 實例化后的對象,具體到本例,可以理解為這樣: [new TestService()]
    const instances = await Promise.all(dependencies.map(resolveParam));
    isResolved && (await callback(instances));
  }

其中調(diào)用 this.getClassDependencies(wrapper) 最終會調(diào)用 reflectConstructorParams

  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;
  }

這里的 PARAMTYPES_METADATA 就是 design:paramtypes。

終于看到了我們想要的結(jié)果,那本文暫時就分析到這里吧,這樣一次帶著一個問題看源碼,目標明確,不至于陷入源碼的汪洋大海之中。

總結(jié)

本文先通過幾個簡單的例子揭示了 TS 中如何實現(xiàn)依賴注入,核心原理在于通過 DecoratorMetadata 兩大特性可以在類及其方法上存儲一些數(shù)據(jù),并且開啟了 emitDecoratorMetadata 后,TS 還可以自動添加三種類型的數(shù)據(jù)。

然后簡單地調(diào)試了 Nest.js 的初始化過程,發(fā)現(xiàn)原理與我們分析的類似。

以上就是Nest.js 之依賴注入原理及實現(xiàn)過程詳解的詳細內(nèi)容,更多關(guān)于Nest.js 依賴注入原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論