前言
在过往工作中,使用了nestjs作为后端服务开发框架。nestjs其中显著的特点是module, service,controller之间的依赖注入,本文记录下对依赖注入原理的学习哈,加强记忆。
什么是依赖注入?
依赖注入是控制反转的一种技术实现。
而控制反转是什么东东呢?举个例子:
classA -> classB
classA内部使用classB的某个方法,在常规的编程下,我们一般通过以下方式进行创建:
1 | class A { |
被依赖的类会在constructor函数进行创建,并且传入相应的参数。
从上面的demo我们可以看出,需要由调用方主动去创建依赖方的实例,并且需要保证所需的参数结构。
这时候我们思考一个问题,在大型的开发项目中,使用面向对象的编程思维,必然会存在错综复杂的依赖关系,所以我们需要维护两个关键代码:
- 实例的创建
- 依赖参数的改动维护
因此,控制反转其实就是为了解决以上问题。
在控制反转的模式下,我们的代码将会变成如下方式:
1 | class A { |
我们可以看到省了classB的创建过程,不需要我们主动去创建classB,而是自动被注入到classA当中,这就是依赖注入基于控制反转设计模式下的实现,目的是就是为了解耦。
简单来说,依赖注入就是将一个对象所依赖的其他对象通过构造函数、方法参数或者属性注入到该对象中,而不是在该对象内部直接创建这些依赖对象。这样做的好处是,我们可以更容易地替换依赖对象,从而实现代码的可测试性和可扩展性。
nestjs中的实现原理
从上面的demo我们只在constructor中传入B的类型,但是typescript只能是在编译是进行静态检查,如何在运行时获取相应classB的类型呢?
这利用到ES7提出的Reflect metadata特性,允许在声明时候添加或获取实例的元数据,具体介绍可以Google了解学习下。
它提供如下3种使用场景:
design:type — 获取属性类型
design:paramtypes — 获取方法参数类型
design:returntype — 获取方法返回类型
1 | class User () { |
那么如果通过Relfect实现一个依赖注入呢?
1 | require("reflect-metadata"); |
测试地址:https://codesandbox.io/s/lingering-moon-xcw6mj?file=/src/index.ts
在nestjs中,搭配使用@Injectable、@Inject()装饰器将class统一放到全局的IOC Container进行管理。启动服务时候,依次去取出class相应的依赖进行实例化。在实际内部中,需要维护class之间的状态,避免重复创建,单例模式等。
有什么坑?
在pnpm monorepo下,主项目通过import方式引入libs下某个nestjs模块,在这个场景下主项目的@nestjs/core与libs的@nestjs/core不是指向同一个内存,在编译阶段导致启动失败。
解决方法:将主项目node_moudles下的libs指向硬链到项目代码具体的libs位置。