Nest.js 之依賴注入原理及實(shí)現(xiàn)過程詳解
前言
很久之前初學(xué) Java 時(shí)就對(duì)注解及自動(dòng)依賴注入這種方式感覺到不可思議,但是一直沒有勇氣(懶)去搞清楚。現(xiàn)在做前端了,發(fā)現(xiàn) Nest.js 里面竟然也是這玩意,終究還是躲不過,那就趁著“陽康”了搞清楚一下吧。
關(guān)于為什么要進(jìn)行依賴注入這里就不展開了,下面直接進(jìn)入正題,TypeScript 依賴注入的原理。
TypeScript 依賴注入的原理
TypeScript 中實(shí)現(xiàn)依賴注入離不開 Decorator 和 Metadata(需要引入第三方庫 reflect-metadata),下面通過一個(gè)簡(jiǎn)單的例子來快速了解它的用途:
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() 這個(gè)裝飾器可以往類及其方法上面添加數(shù)據(jù),然后通過 Reflect.getMetadata 可以取到這些數(shù)據(jù)。我們可以借助這一特性,實(shí)現(xiàn)簡(jiǎ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
}
}
開啟了這個(gè)參數(shù)后,我們就不需要手動(dòng)添加元數(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 自動(dòng)會(huì)在我們的裝飾器前添加一些裝飾器。比如,下面這段代碼:
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 自動(dòng)會(huì)添加 design:type|paramtypes|returntype 三種類型的元數(shù)據(jù),分別表示目標(biāo)本身,參數(shù)以及返回值的類型。
我們把 inject 稍微改一下,支持遞歸的注入,這樣一個(gè)簡(jiǎn)單的依賴注入就實(shí)現(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 大概是怎么實(shí)現(xiàn)的。
淺析 Nest.js 實(shí)現(xiàn)依賴注入的過程
我們通過官方腳手架生成一個(gè) Demo 項(xiàng)目,可以發(fā)現(xiàn)其中 tsconfig.json 中的 emitDecoratorMetadata 確實(shí)是開啟的。我們先用一個(gè)最簡(jiǎn)單的例子來說明:
// 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()
為了更加直觀的理解流程,這里暫時(shí)先把源碼核心部分扒下來,我們把 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裝飾器所聲明的,我們這里暫時(shí)只考慮providers。這一步執(zhí)行完后,NestContainer中數(shù)據(jù)如下(注意到Module本身也作為自己的provider):
{
modules: {
'19bb8f429cacdbcc18fc1afcaac891a4606578aa': Module {
_metatype: class AppModule {...},
_providers: {
class AppModule {...}: InstanceWrapper {}, // Module 本身也作為自己的 provider
class TestService {...}: InstanceWrapper {}
}
}
}
}
- 實(shí)例化
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);
...
}),
);
}
這里的意思是實(shí)例化所有的 Module,實(shí)例化 Module 前,我們需要先實(shí)例化它的依賴,具體到這里就是實(shí)例化 providers:
private async createInstancesOfProviders(moduleRef: Module) {
const { providers } = moduleRef;
const wrappers = [...providers.values()];
await Promise.all(
wrappers.map(item => this.injector.loadProvider(item, moduleRef)),
);
}
最后會(huì)到 loadInstance 這個(gè)函數(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 這個(gè)函數(shù)了,我們以 class AppModule 這個(gè) 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 實(shí)例化后的對(duì)象,具體到本例,可以理解為這樣: [new TestService()]
const instances = await Promise.all(dependencies.map(resolveParam));
isResolved && (await callback(instances));
}
其中調(diào)用 this.getClassDependencies(wrapper) 最終會(huì)調(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é)果,那本文暫時(shí)就分析到這里吧,這樣一次帶著一個(gè)問題看源碼,目標(biāo)明確,不至于陷入源碼的汪洋大海之中。
總結(jié)
本文先通過幾個(gè)簡(jiǎn)單的例子揭示了 TS 中如何實(shí)現(xiàn)依賴注入,核心原理在于通過 Decorator 及 Metadata 兩大特性可以在類及其方法上存儲(chǔ)一些數(shù)據(jù),并且開啟了 emitDecoratorMetadata 后,TS 還可以自動(dòng)添加三種類型的數(shù)據(jù)。
然后簡(jiǎn)單地調(diào)試了 Nest.js 的初始化過程,發(fā)現(xiàn)原理與我們分析的類似。
以上就是Nest.js 之依賴注入原理及實(shí)現(xiàn)過程詳解的詳細(xì)內(nèi)容,更多關(guān)于Nest.js 依賴注入原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Babel?插件開發(fā)&訪問節(jié)點(diǎn)實(shí)例詳解
這篇文章主要為答案及介紹了Babel?插件開發(fā)&訪問節(jié)點(diǎn)實(shí)例詳解,整理一下?Babel?插件開發(fā)時(shí)用得到的轉(zhuǎn)換操作相關(guān)的?API,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
腳本整合指定文件/文件夾執(zhí)行定制化ESLint命令使用實(shí)例
這篇文章主要為大家介紹了腳本整合指定文件/文件夾執(zhí)行定制化?ESLint命令使用實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
js?交互在Flutter?中使用?webview_flutter
這篇文章主要為大家介紹了js?交互在Flutter?中使用?webview_flutter示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
微信小程序 action-sheet詳解及實(shí)例代碼
這篇文章主要介紹了微信小程序 action-sheet詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-11-11
JS前端可擴(kuò)展的低代碼UI框架Sunmao使用詳解
這篇文章主要為大家介紹了JS前端可擴(kuò)展的低代碼UI框架Sunmao使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
微信小程序 在Chrome瀏覽器上運(yùn)行以及WebStorm的使用
這篇文章主要介紹了微信小程序 在Chrome瀏覽器上運(yùn)行以及WebStorm的使用的相關(guān)資料,需要的朋友可以參考下2016-09-09
JS常用正則表達(dá)式超全集(密碼強(qiáng)度校驗(yàn),金額校驗(yàn),IE版本,IPv4,IPv6校驗(yàn))
網(wǎng)上有很多關(guān)于JS常用正則表達(dá)式的文章很全但今天為大家分享一些最新,且非常有用的正則表達(dá)式其中有密碼強(qiáng)度校驗(yàn),金額校驗(yàn),IE版本,IPv4,IPv6校驗(yàn)等2020-02-02

