从reflect?metadata理解Nest实现原理

news2024/12/23 17:18:03

目录

  • 正文
  • 入口Module 引入模块
  • CatsService操作数据库
  • Reflect Metadata
  • 那元数据存在哪呢?
  • nest 的源码:
  • 总结

正文

Nest 是 Node.js 的服务端框架,它最出名的就是 IOC(inverse of control) 机制了,也就是不需要手动创建实例,框架会自动扫描需要加载的类,并创建他们的实例放到容器里,实例化时还会根据该类的构造器参数自动注入依赖。

它一般是这样用的:

入口Module 引入模块

比如入口 Module 里引入某个模块的 Module:

?

1

2

3

4

5

6

import { Module } from '@nestjs/common';

import { CatsModule } from './cats/cats.module';

@Module({

  imports: [CatsModule],

})

export class AppModule {}

然后这个模块的 Module 里会声明 Controller 和 Service:

?

1

2

3

4

5

6

7

8

import { Module } from '@nestjs/common';

import { CatsController } from './cats.controller';

import { CatsService } from './cats.service';

@Module({

  controllers: [CatsController],

  providers: [CatsService],

})

export class CatsModule {}

Controller 里就是声明 url 对应的处理逻辑:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import { Body, Controller, Get, Param, Post } from '@nestjs/common';

import { CatsService } from './cats.service';

import { CreateCatDto } from './dto/create-cat.dto';

@Controller('cats')

export class CatsController {

  constructor(private readonly catsService: CatsService) {}

  @Post()

  async create(@Body() createCatDto: CreateCatDto) {

    this.catsService.create(createCatDto);

  }

  @Get()

  async findAll(): Promise<Cat[]> {

    return this.catsService.findAll();

  }

}

这个 CatsController 的构造器声明了对 CatsService 的依赖:

然后 CatsService 里就可以去操作数据库进行增删改查了。

CatsService操作数据库

这里简单实现一下:

?

1

2

3

4

5

6

7

8

9

10

11

12

import { Injectable } from '@nestjs/common';

import { Cat } from './interfaces/cat.interface';

@Injectable()

export class CatsService {

  private readonly cats: Cat[] = [];

  create(cat: Cat) {

    this.cats.push(cat);

  }

  findAll(): Cat[] {

    return this.cats;

  }

}

之后在入口处调用 create 把整个 nest 应用跑起来:

?

1

2

3

4

5

6

7

import { NestFactory } from '@nestjs/core';

import { AppModule } from './app.module';

async function bootstrap() {

  const app = await NestFactory.create(AppModule);

  await app.listen(3000);

}

bootstrap();

然后浏览器访问下我们写的那个 controller 对应的 url,打个断点:

你会发现 controller 的实例已经创建了,而且 service 也给注入了。这就是依赖注入的含义。

这种机制就叫做 IOC(控制反转),也叫依赖注入,好处是显而易见的,就是只需要声明依赖关系,不需要自己创建对象,框架会扫描声明然后自动创建并注入依赖。

Java 里最流行的 Spring 框架就是 IOC 的实现,而 Nest 也是这样一个实现了 IOC 机制的 Node.js 的后端框架。

不知道大家有没有感觉很神奇,只是通过装饰器声明了一下,然后启动 Nest 应用,这时候对象就给创建好了,依赖也给注入了。

那它是怎么实现的呢?

大家如果就这样去思考它的实现原理,还真不一定能想出来,因为缺少了一些前置知识。也就是实现 Nest 最核心的一些 api: Reflect 的 metadata 的 api。

Reflect Metadata

有的同学会说,Reflect 的 api 我很熟呀,就是操作对象的属性、方法、构造器的一些 api:

比如 Reflect.get 是获取对象属性值

Reflect.set 是设置对象属性值

Reflect.has 是判断对象属性是否存在

Reflect.apply 是调用某个方法,传入对象和参数

 

Reflect.construct 是用构造器创建对象实例,传入构造器参数

这些 api 在 MDN 文档里可以查到,因为它们都已经是 es 标准了,也被很多浏览器实现了。

但是实现 Nest 用到的 api 还没有进入标准,还在草案阶段,也就是 metadata 的 api:

它有这些 api:

?

1

2

3

4

Reflect.defineMetadata(metadataKey, metadataValue, target);

Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);

let result = Reflect.getMetadata(metadataKey, target);

let result = Reflect.getMetadata(metadataKey, target, propertyKey);

Reflect.defineMetadata 和 Reflect.getMetadata 分别用于设置和获取某个类的元数据,如果最后传入了属性名,还可以单独为某个属性设置元数据。

那元数据存在哪呢?

存在类或者对象上呀,如果给类或者类的静态属性添加元数据,那就保存在类上,如果给实例属性添加元数据,那就保存在对象上,用类似 [[metadata]] 的 key 来存的。

这有啥用呢?

看上面的 api 确实看不出啥来,但它也支持装饰器的方式使用:

?

1

2

3

4

5

6

@Reflect.metadata(metadataKey, metadataValue)

class C {

  @Reflect.metadata(metadataKey, metadataValue)

  method() {

  }

}

Reflect.metadata 装饰器当然也可以再封装一层:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

function Type(type) {

    return Reflect.metadata("design:type", type);

}

function ParamTypes(...types) {

    return Reflect.metadata("design:paramtypes", types);

}

function ReturnType(type) {

    return Reflect.metadata("design:returntype", type);

}

@ParamTypes(String, Number)

class Guang {

  constructor(text, i) {

  }

  @Type(String)

  get name() { return "text"; }

  @Type(Function)

  @ParamTypes(Number, Number)

  @ReturnType(Number)

  add(x, y) {

    return x + y;

  }

}

然后我们就可以通过 Reflect metadata 的 api 或者这个类和对象的元数据了:

?

1

2

3

let obj = new Guang("a", 1);

let paramTypes = Reflect.getMetadata("design:paramtypes", obj, "add");

// [Number, Number]

这里我们用 Reflect.getMetadata 的 api 取出了 add 方法的参数的类型。

看到这里,大家是否明白 nest 的原理了呢?

nest 的源码:

上面就是 @Module 装饰器的实现,里面就调用了 Reflect.defineMetadata 来给这个类添加了一些元数据。

所以我们这样用的时候:

?

1

2

3

4

5

6

7

8

import { Module } from '@nestjs/common';

import { CatsController } from './cats.controller';

import { CatsService } from './cats.service';

@Module({

  controllers: [CatsController],

  providers: [CatsService],

})

export class CatsModule {}

其实就是给 CatsModule 添加了 controllers 的元数据和 providers 的元数据。

后面创建 IOC 容器的时候就会取出这些元数据来处理:

而且 @Controller 和 @Injectable 的装饰器也是这样实现的:

 

看到这里,大家是否想明白 nest 的实现原理了呢?

其实就是通过装饰器给 class 或者对象添加元数据,然后初始化的时候取出这些元数据,进行依赖的分析,然后创建对应的实例对象就可以了。

所以说,nest 实现的核心就是 Reflect metadata 的 api。

当然,现在 metadata 的 api 还在草案阶段,需要使用 reflect-metadata 这个 polyfill 包才行。

其实还有一个疑问,依赖的扫描可以通过 metadata 数据,但是创建的对象需要知道构造器的参数,现在并没有添加这部分 metadata 数据呀:

比如这个 CatsController 依赖了 CatsService,但是并没有添加 metadata 呀:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import { Body, Controller, Get, Param, Post } from '@nestjs/common';

import { CatsService } from './cats.service';

import { CreateCatDto } from './dto/create-cat.dto';

@Controller('cats')

export class CatsController {

  constructor(private readonly catsService: CatsService) {}

  @Post()

  async create(@Body() createCatDto: CreateCatDto) {

    this.catsService.create(createCatDto);

  }

  @Get()

  async findAll(): Promise<Cat[]> {

    return this.catsService.findAll();

  }

}

这就不得不提到 TypeScript 的优势了,TypeScript 支持编译时自动添加一些 metadata 数据:

比如这段代码:

?

1

2

3

4

5

6

7

import "reflect-metadata";

class Guang {

  @Reflect.metadata("名字", "光光")

  public say(a: number): string {

    return '加油鸭';

  }

}

按理说我们只添加了一个元数据,生成的代码也确实是这样的:

但是呢,ts 有一个编译选项叫做 emitDecoratorMetadata,开启它就会自动添加一些元数据。

开启之后再试一下:

你会看到多了三个元数据:

design:type 是 Function,很明显,这个是描述装饰目标的元数据,这里装饰的是函数

design:paramtypes 是 [Number],很容易理解,就是参数的类型

design:returntype 是 String,也很容易理解,就是返回值的类型

所以说,只要开启了这个编译选项,ts 生成的代码会自动添加一些元数据。

然后创建对象的时候就可以通过 design:paramtypes 来拿到构造器参数的类型了,那不就知道怎么注入依赖了么?

所以,nest 源码里你会看到这样的代码:

就是获取构造器的参数类型的。这个常量就是我们上面说的那个:

这也是为什么 nest 会用 ts 来写,因为它很依赖这个 emitDecoratorMetadata 的编译选项。

你用 cli 生成的代码模版里也都默认开启了这个编译选项:

 

这就是 nest 的核心实现原理:通过装饰器给 class 或者对象添加 metadata,并且开启 ts 的 emitDecoratorMetadata 来自动添加类型相关的 metadata,然后运行的时候通过这些元数据来实现依赖的扫描,对象的创建等等功能。

总结

Nest 是 Node.js 的后端框架,他的核心就是 IOC 容器,也就是自动扫描依赖,创建实例对象并且自动依赖注入。

要搞懂它的实现原理,需要先学习 Reflect metadata 的 api:

这个是给类或者对象添加 metadata 的。可以通过 Reflect.metadata 给类或者对象添加元数据,之后用到这个类或者对象的时候,可以通过 Reflect.getMetadata 把它们取出来。

Nest 的 Controller、Module、Service 等等所有的装饰器都是通过 Reflect.meatdata 给类或对象添加元数据的,然后初始化的时候取出来做依赖的扫描,实例化后放到 IOC 容器里。

实例化对象还需要构造器参数的类型,这个开启 ts 的 emitDecoratorMetadata 的编译选项之后, ts 就会自动添加一些元数据,也就是 design:type、design:paramtypes、design:returntype 这三个,分别代表被装饰的目标的类型、参数的类型、返回值的类型。

当然,reflect metadata 的 api 还在草案阶段,需要引入 refelect metadata 的包做 polyfill。

nest 的一系列装饰器就是给 class 和对象添加 metadata 的,然后依赖扫描和依赖注入的时候就把 metadata 取出来做一些处理。

理解了 metadata,nest 的实现原理就很容易搞懂了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/644940.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mocha Pro:Track 模块

Track&#xff08;跟踪&#xff09;模块中提供了几组选项&#xff0c;进行适当设置之后再实施跟踪&#xff0c;可以得到更好的跟踪结果。 ◆ ◆ ◆ 模块选项说明 Input 输入 Clip 剪辑 选择要跟踪的素材。 --Input 输入 --Layer Below 图层下方 Track Individual Fields 跟…

零基础想转行做python爬虫及数据分析方向的程序员,有哪些书可以推荐?

学习Python语言是一个不错的选择&#xff0c;一方面Python的应用广泛&#xff0c;在大数据、人工智能、Web开发等领域有大量的使用&#xff0c;另一方面Python语言本身比较简单&#xff0c;非常适合初学者。 Python是完全可以自学的&#xff0c;如果英语基础还可以的话&#x…

有什么软件可以翻译文档?这几款文本翻译软件效果不错

随着世界的全球化&#xff0c;我们越来越需要通过语言进行跨文化交流。但是&#xff0c;不同国家和地区使用的语言却存在差异&#xff0c;这就需要我们掌握一些文本翻译技巧。那么&#xff0c;你是否想过如何实现文本翻译呢&#xff1f;在本文中&#xff0c;我们将给你介绍一些…

Linux:vi编辑器

Vi/vim是visual interface的缩写&#xff0c;是Linux中的文本编辑器&#xff0c;vim相当于是vi的加强版。 1、vi/vim编辑器的三种工作模式&#xff1a; 命令模式&#xff1a;连按两次y键&#xff0c;再按p键&#xff0c;表示复制粘贴当前行的内容&#xff1b;连按两次d键&#…

MySQL数据库---库基本操作 以及 表结构的操作(DDL)

目录 前言 一.数据库的操作 1.1显示当前数据库 1.2创建数据库 1.3使用数据库 1.4删除数据库 二.数据类型 2.1数值类型 2.2字符串类型 2.3日期类型 三.数据表的操作 3.1 创建表结构。 3.2查看数据库中拥有的数据表 3.3查看指定的表结构 3.4修改表结构 3.3删除表结构 …

2023-6-10-第五式原型模式

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…

vue3-实战-09-管理后台-SKU模块开发

目录 1-需求原型分析 2-功能模块开发 2.1-列表页面数据获取和展示 2.2-上架下架sku 2.3-更新sku信息 2.4-查看sku详情 2.5-删除sku 1-需求原型分析 列表页面就是el-card里面放置el-table结构&#xff0c;下面有个el-pagination组件显示分页器。点击查看详情的时候有个抽…

java SSM 学生住宿管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM 学生住宿管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采…

【指针数组】

指针数组 1. 指针运算1.1 指针-整数1.2 指针-指针1.3 指针的关系运算 2. 指针和数组3. 二级指针4. 指针数组 1. 指针运算 指针 整数 指针-指针 指针的关系运算 1.1 指针整数 #define N_VALUES 5 float values[N_VALUES]; float *vp; //指针-整数&#xff1b;指针的关系运算 …

基于Python的招聘信息可视化系统,附源码

文章目录 1 简介2 技术栈3 总体设计3.1 系统结构3.2 数据库设计3.2.1 数据库实体3.2.2 数据库表设计 4 运行设计4.1 招聘热门行业分析4.2热门岗位分析界面4.3招聘岗位学历分析界面4.4岗位分布分析界面 5 源码下载 1 简介 基于Python的招聘信息可视化系统,通过对招聘数据进行分…

界面开发框架Qt新手入门指南 - 使用Calendar组件创建日历(一)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 本文中的CalendarWi…

docker ansible与剧本模式

ansible&#xff08;跨主机编排&#xff09; ansible 是一个基于python开发的配置管理和应用部署和管理工具&#xff0c;现在也在自动化管理领域大放异彩&#xff0c;他融合了众多老牌运维工具的优点&#xff0c;pubbet和saltstack能实现的功能&#xff0c;ansible基本上都可以…

antd-vue - - - - - table增加统计行?

table增加统计行 尝试一、footer & Summary使用summary尝试二、直接将统计行push进dataSource 第一次遇到这个需求&#xff0c;有点懵。 在【antd-v table】官网仔细看了一番&#xff0c;找到这么两个配置footer[表格尾部]和Summary[总结栏]  所以可以证明&#xff0c;你所…

TDEngine3.x数据查询及插入调优

一、数据库创建 vgroups 配置 如果不了解vgroup概念&#xff0c;建议到官网查看&#xff1a;TDEngine官网-数据模型和整体架构 从服务端配置的角度&#xff0c;要根据系统中磁盘的数量&#xff0c;磁盘的 I/O 能力&#xff0c;以及处理器能力在创建数据库时设置适当的 vgroups…

Qt编写onvif工具(搜索/云台/预置位/OSD/录像存储)

一、前言 从最初编写这个工具开始的时间算起来&#xff0c;至少5年多&#xff0c;一直持续完善到今天&#xff0c;这个工具看起来小也不小大也不大&#xff0c;但是也是经历过无数个现场的洗礼&#xff0c;毫不夸张的说&#xff0c;市面上能够遇到的主流的厂商的设备&#xff…

【Flutter】如何 Dialog 弹窗设置点击空白处不关闭

文章目录 一、 引言二、 Flutter 中的 Dialog 弹窗1. 默认的 Dialog 行为介绍2. 解释为什么在某些情况下我们需要点击空白处不关闭 Dialog 三、 如何在 Flutter 中设置 Dialog 弹窗点击空白处不关闭1. 展示简单的代码示例2. 详细解释代码的每个部分 四、 一个完整的 Flutter Di…

SpringCloud服务注册与发现组件Eureka(五)

Eureka github 地址&#xff1a; https://github.com/Netflix/eureka Eureka简介 Eureka是Netflix开发的服务发现框架&#xff0c;本身是一个基于REST的服务&#xff0c;主要用于定位运行在AWS域中的中间层服务&#xff0c;以达到负载均衡和中间层服务故障转移的目的。Spring…

迈入大模型时代,多模态AI通用化成未来趋势,景联文科技提供多模态数据集

ChatGPT带来2023年第一个火爆的风口。ChatGPT是人工智能技术驱动的自然语言处理工具&#xff0c;拥有语言理解和文本生成能力。无论是强大的视频脚本、文案、邮件、翻译、代码等内容生成能力&#xff0c;还是语义推理、情绪分析等对话能力&#xff0c;都让大众眼前一亮&#xf…

C++类和对象(继承)

4.6继承 继承是面向对象三大特性之一 有些类与类之间存在特殊的关系&#xff0c;例如下图中&#xff1a; 我们发现&#xff0c;定义这些类时&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性。 这个时候我们就可以考虑利用继承的技术&#xff0c;减…

阿里云PAIx达摩院GraphScope开源基于PyTorch的GPU加速分布式GNN框架

作者&#xff1a;艾宝乐 导读 近期阿里云机器学习平台 PAI 团队和达摩院 GraphScope 团队联合推出了面向 PyTorch 的 GPU 加速分布式 GNN 框架 GraphLearn-for-PyTorch(GLT) 。GLT 利用 GPU 的强大并行计算性能来加速图采样&#xff0c;并利用 UVA 来减少顶点和边特征的转换和…