了解Node.js Nestjs框架的模块机制,聊聊实现原理( 三 )


3 Nestjs的模块(@Module)Nestjs实现了控制反转 , 约定配置模块(@module)的 imports、exports、providers 管理提供者也就是类的依赖注入 。
providers 可以理解是在当前模块注册和实例化类 , 下面的 A 和 B 就在当前模块被实例化 , 如果B在构造函数中引用 A , 就是引用的当前 ModuleD 的 A 实例 。
import { Module } from '@nestjs/common';import { ModuleX } from './moduleX';import { A } from './A';import { B } from './B';@Module({ imports: [ModuleX], providers: [A,B], exports: [A]})export class ModuleD {}// Bclass B{ constructor(a:A){ this.a = a; }}exports 就是把当前模块中的 providers 中实例化的类 , 作为可被外部模块共享的类 。 比如现在 ModuleF 的 C 类实例化的时候 , 想直接注入 ModuleD 的 A 类实例 。 就在 ModuleD 中设置导出(exports)A , 在 ModuleF 中通过 imports 导入 ModuleD 。
按照下面的写法 , 控制反转程序会自动扫描依赖 , 首先看自己模块的 providers 中 , 有没有提供者 A , 如果没有就去寻找导入的 ModuleD 中是否有 A 实例 , 发现存在 , 就取得 ModuleD 的 A 实例注入到 C 实例之中 。
import { Module } from '@nestjs/common';import { ModuleD} from './moduleD';import { C } from './C';@Module({ imports: [ModuleD], providers: [C],})export class ModuleF {}// Cclass C { constructor(a:A){ this.a = a; }}因此想要让外部模块使用当前模块的类实例 , 必须先在当前模块的providers里定义实例化类 , 再定义导出这个类 , 否则就会报错 。
//正确@Module({ providers: [A], exports: [A]})//错误@Module({ providers: [], exports: [A]})

后期补充模块查找实例的过程回看了一下 , 确实有点不清晰 。 核心点就是providers里的类会被实例化 , 实例化后就是提供者 , 模块里只有providers里的类会被实例化 , 而导出和导入只是一个组织关系配置 。 模块会优先使用自己的提供者 , 如果没有 , 再去找导入的模块是否有对应提供者
这里还是提一嘴ts的知识点
export class C { constructor(private a: A) { }}由于 TypeScript 支持 constructor 参数(private、protected、public、readonly)隐式自动定义为 class 属性 (Parameter Property) , 因此无需使用 this.a = a 。 Nest 中都是这样的写法 。
4 Nest 元编程元编程的概念在 Nest 框架中得到了体现 , 它其中的控制反转、装饰器 , 就是元编程的实现 。 大概可以理解为 , 元编程本质还是编程 , 只是中间多了一些抽象的程序 , 这个抽象程序能够识别元数据(如@Module中的对象数据) , 其实就是一种扩展能力 , 能够将其他程序作为数据来处理 。 我们在编写这样的抽象程序 , 就是在元编程了 。
4.1 元数据
Nest 文档中也常提到了元数据 , 元数据这个概念第一次看到的话 , 也会比较费解 , 需要随着接触时间增长习惯成理解 , 可以不用太过纠结 。
元数据的定义是:描述数据的数据 , 主要是描述数据属性的信息 , 也可以理解为描述程序的数据 。
Nest 中 @Module 配置的exports、providers、imports、controllers都是元数据 , 因为它是用来描述程序关系的数据 , 这个数据信息不是展示给终端用户的实际数据 , 而是给框架程序读取识别的 。
4.2 Nest 装饰器
如果看看 Nest 中的装饰器源码 , 会发现 , 几乎每一个装饰器本身只是通过 reflect-metadata 定义了一个元数据 。
@Injectable装饰器
export function Injectable(options?: InjectableOptions): ClassDecorator { return (target: object) => { Reflect.defineMetadata(INJECTABLE_WATERMARK, true, target); Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, options, target); };}这里存在反射的概念 , 反射也比较好理解 , 拿 @Module 装饰器举例 , 定义元数据 providers , 只是往providers数组里传入了类 , 在程序实际运行时providers里的类 , 会被框架程序自动实例化变为提供者 , 不需要开发者显示的去执行实例化和依赖注入 。 类只有在模块中实例化了之后才变成了提供者 。 providers中的类被反射了成了提供者 , 控制反转就是利用的反射技术 。
换个例子的话 , 就是数据库中的 ORM(对象关系映射) , 使用 ORM 只需要定义表字段 , ORM 库会自动把对象数据转换为 SQL 语句 。
const data = https://www.52zixue.com/zhanzhang/webqd/js/04/09/69299/TableModel.build();data.time = 1;data.browser = 'chrome'; data.save();// SQL: INSERT INTO tableName (time,browser) [{"time":1,"browser":"chrome"}]

推荐阅读