前言
很久之前初学 Java
时就对注解及自动依赖注入这种方式感觉到不可思议,但是一直没有勇气(懒)去搞清楚。现在做前端了,发现 Nest.js 里面竟然也是这玩意,终究还是躲不过,那就趁着“阳康”了搞清楚一下吧。
关于为什么要进行依赖注入这里就不展开了,下面直接进入正题,TypeScript 依赖注入的原理。
TypeScript 依赖注入的原理
TypeScript 中实现依赖注入离不开 Decorator
和 Metadata
(需要引入第三方库 reflect-metadata
),下面通过一个简单的例子来快速了解它的用途:
1 | import 'reflect-metadata' |
通过例子可以看到,我们通过 Reflect.metadata()
这个装饰器可以往类及其方法上面添加数据,然后通过 Reflect.getMetadata
可以取到这些数据。我们可以借助这一特性,实现简单的依赖注入:
1 | import 'reflect-metadata' |
如上所示,我们通过 `@Reflect.metadata(‘params’, [TestService])在
Test上添加了元数据,表示构造函数中需要用到
TestService,但
Nest.js` 中好像不需要这样。怎么办呢?答案就是:
1 | { |
开启了这个参数后,我们就不需要手动添加元数据了:
1 | import 'reflect-metadata' |
原因在于开启 emitDecoratorMetadata
后,TS 自动会在我们的装饰器前添加一些装饰器。比如,下面这段代码:
1 | import 'reflect-metadata' |
编译过后是这样子的:
1 | var __decorate = ... |
可以看到,TS 自动会添加 design:type|paramtypes|returntype
三种类型的元数据,分别表示目标本身,参数以及返回值的类型。
我们把 inject
稍微改一下,支持递归的注入,这样一个简单的依赖注入就实现了:
1 | import 'reflect-metadata' |
接下来,我们浅看一下 Nest.js
大概是怎么实现的。
浅析 Nest.js 实现依赖注入的过程
我们通过官方脚手架生成一个 Demo 项目,可以发现其中 tsconfig.json
中的 emitDecoratorMetadata
确实是开启的。我们先用一个最简单的例子来说明:
1 | // app.module.ts |
为了更加直观的理解流程,这里暂时先把源码核心部分扒下来,我们把 await NestFactory.create(AppModule)
替换成我们自己的代码:
1 | const injector = new Injector() |
1 | import {Type} from '@nestjs/common' |
这里大概分成两部分:
- 处理
module
的依赖,也就是@Module
装饰器所声明的,我们这里暂时只考虑providers
。这一步执行完后,NestContainer
中数据如下(注意到Module
本身也作为自己的provider
):
1 | { |
- 实例化
Module
。这一部分需要稍微看一下源码:
1 | public async createInstancesOfDependencies( |
这里的意思是实例化所有的 Module
,实例化 Module
前,我们需要先实例化它的依赖,具体到这里就是实例化 providers
:
1 | private async createInstancesOfProviders(moduleRef: Module) { |
最后会到 loadInstance
这个函数:
1 | public async loadInstance<T>( |
接下来就到了最重要的 this.resolveConstructorParams
这个函数了,我们以 class AppModule
这个 provider
为例来分析:
1 | public async resolveConstructorParams<T>( |
其中调用 this.getClassDependencies(wrapper)
最终会调用 reflectConstructorParams
:
1 | public reflectConstructorParams<T>(type: Type<T>): any[] { |
这里的 PARAMTYPES_METADATA
就是 design:paramtypes
。
终于看到了我们想要的结果,那本文暂时就分析到这里吧,这样一次带着一个问题看源码,目标明确,不至于陷入源码的汪洋大海之中。
总结
本文先通过几个简单的例子揭示了 TS 中如何实现依赖注入,核心原理在于通过 Decorator
及 Metadata
两大特性可以在类及其方法上存储一些数据,并且开启了 emitDecoratorMetadata
后,TS 还可以自动添加三种类型的数据。
然后简单地调试了 Nest.js
的初始化过程,发现原理与我们分析的类似。