Nestjs依赖注入原理

49 阅读2分钟

前言

业务项目使用到 Nestjs 作为Nodejs开发框架,Nestjs 明显区别于 Eggjs 基于约定基于配置的组织方式,采用了依赖注入的方式管理模块。那 Nestjs 是如何实现依赖注入的呢?本文章将对此进行解析。

Nestjs中的依赖注入

@Controller('cats')
export class CatsController1 {    
    constructor(private catsService: CatsService) {}
}

在这个例子中, 可以看到 CatsController 依赖了 CatsService,然后在执行时 Nestjs 运行时会将 CatsService 的实例传递给 CatsController。

@Controller('cats')
export class CatsController2 {    
    constructor(private catsService: CatsService, private dogsService:DogsService) {}
}

在这个例子中,CatsController 依赖了 CatsService 和 DogsService,在执行时 Nestjs依然会准确将对应的实例传递给 CatsController。即使 CatsService 和 DogsService 的参数位置对调,实例传递依然是正确的。

一个疑问

Nestjs是如何知道某个参数所对应的类?虽然我们写的代码是TS,但在实际Nodejs运行时,运行的是JS代码,而JS在运行时是没有类型的,也就是说理论上Nestjs运行时并不知道参数对应的类型,但它仍然做对了,是不是很神奇?那接下来我将对这个问题进行解答,而这个问题的奥秘就在于装饰器。

装饰器

装饰器在Class中支持以下几种模式:

  • Class
  • Class属性
  • Class方法
  • Class getter/setter
  • class方法参数

Nestjs运用了上述大部分装饰器模式,那装饰器在依赖注入的过程中起到了什么样的作用呢?我们来看一下经过转换后的代码。

var CatsController1 = /** @class */ (function () {  
    function CatsController1(catsService) {    
        this.catsService = catsService;  
     }  
     CatsController1 = __decorate([    
         (0, common_1.Controller)('cats'),   
         __metadata("design:paramtypes", [CatsService])  ], 
         CatsController1);  
         return CatsController1;
}());

exports.CatsController1 = CatsController1;
var CatsController2 = /** @class */ (function () {  
    function CatsController2(catsService, dogsService) {    
        this.catsService = catsService;    
        this.dogsService = dogsService;  
     }  
     CatsController2 = __decorate([    
         (0, common_1.Controller)('cats'),    
         __metadata("design:paramtypes", [ CatsService,  DogsService]) ],CatsController2);  
         return CatsController2
      ;}());
 exports.CatsController2 = CatsController2;

可以看到这其中有一个特殊的字符串 design:paramtypes,那这个字符串代表了什么呢?

元数据

这里就涉及到元数据相关的知识,具体可参考规范[规范](rbuckton.github.io/reflect-met… 作为预定义的设计时类型注释,它分为以下三种类型:

  • design:type 装饰目标的类型
  • design:paramtypes 装饰目标的参数类型,会严格一一对应参数位置
  • design:returntype 装饰目标的返回类型

基于以上三种类型,可以通过 ReflectMetadata 的 getMetadata 方法准确获取到目标的参数类型。

结尾

到这里,想必聪明的你可以猜到,Nestjs 正是利用了这个特性实现了上述问题的解决方案。也由于这个方案的需求,Nestjs强依赖于 typescript 的编译器的 emitDecoratorMetadata 特性。本文主要讲解了 Nestjs 如何实现精准的注入依赖,那 Nestjs 运行时是如何进行组织、管理呢?下篇将详细解析 Nestjs 初始化全流程。