reflect metadata Nest实现原理是什么
导读:本文共6250字符,通常情况下阅读需要21分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要:正文Nest 是 Node.js 的服务端框架,它最出名的就是 IOC(inverse of control) 机制了,也就是不需要手动创建实例,框架会自动扫描需要加载的类,并创建他们的实例放到容器里,实例化时还会根据该类的构造器参数自动注入依赖。入口Module 引入模块比如入口 Module 里引入某个模块的 Module:import{Module}from'@nestjs/comm... ...
目录
(为您整理了一些要点),点击可以直达。正文
Nest 是 Node.js 的服务端框架,它最出名的就是 IOC(inverse of control) 机制了,也就是不需要手动创建实例,框架会自动扫描需要加载的类,并创建他们的实例放到容器里,实例化时还会根据该类的构造器参数自动注入依赖。
入口Module 引入模块
比如入口 Module 里引入某个模块的 Module:
import{Module}from'@nestjs/common';
import{CatsModule}from'./cats/cats.module';
@Module({
imports:[CatsModule],
})
exportclassAppModule{}
然后这个模块的 Module 里会声明 Controller 和 Service:
import{Module}from'@nestjs/common';
import{CatsController}from'./cats.controller';
import{CatsService}from'./cats.service';
@Module({
controllers:[CatsController],
providers:[CatsService],
})
exportclassCatsModule{}
Controller 里就是声明 url 对应的处理逻辑:
import{Body,Controller,Get,Param,Post}from'@nestjs/common';
import{CatsService}from'./cats.service';
import{CreateCatDto}from'./dto/create-cat.dto';
@Controller('cats')
exportclassCatsController{
constructor(privatereadonlycatsService:CatsService){}
@Post()
asynccreate(@Body()createCatDto:CreateCatDto){
this.catsService.create(createCatDto);
}
@Get()
asyncfindAll():Promise<Cat[]>{
returnthis.catsService.findAll();
}
}
这个 CatsController 的构造器声明了对 CatsService 的依赖:
然后 CatsService 里就可以去操作数据库进行增删改查了。
CatsService操作数据库
这里简单实现一下:
import{Injectable}from'@nestjs/common';
import{Cat}from'./interfaces/cat.interface';
@Injectable()
exportclassCatsService{
privatereadonlycats:Cat[]=[];
create(cat:Cat){
this.cats.push(cat);
}
findAll():Cat[]{
returnthis.cats;
}
}
之后在入口处调用 create 把整个 nest 应用跑起来:
import{NestFactory}from'@nestjs/core';
import{AppModule}from'./app.module';
asyncfunctionbootstrap(){
constapp=awaitNestFactory.create(AppModule);
awaitapp.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:
Reflect.defineMetadata(metadataKey,metadataValue,target);
Reflect.defineMetadata(metadataKey,metadataValue,target,propertyKey);
letresult=Reflect.getMetadata(metadataKey,target);
letresult=Reflect.getMetadata(metadataKey,target,propertyKey);
Reflect.defineMetadata 和 Reflect.getMetadata 分别用于设置和获取某个类的元数据,如果最后传入了属性名,还可以单独为某个属性设置元数据。
那元数据存在哪呢?
存在类或者对象上呀,如果给类或者类的静态属性添加元数据,那就保存在类上,如果给实例属性添加元数据,那就保存在对象上,用类似 [[metadata]] 的 key 来存的。
这有啥用呢?
看上面的 api 确实看不出啥来,但它也支持装饰器的方式使用:
@Reflect.metadata(metadataKey,metadataValue)
classC{
@Reflect.metadata(metadataKey,metadataValue)
method(){
}
}
Reflect.metadata 装饰器当然也可以再封装一层:
functionType(type){
returnReflect.metadata("design:type",type);
}
functionParamTypes(...types){
returnReflect.metadata("design:paramtypes",types);
}
functionReturnType(type){
returnReflect.metadata("design:returntype",type);
}
@ParamTypes(String,Number)
classGuang{
constructor(text,i){
}
@Type(String)
getname(){return"text";}
@Type(Function)
@ParamTypes(Number,Number)
@ReturnType(Number)
add(x,y){
returnx+y;
}
}
然后我们就可以通过 Reflect metadata 的 api 或者这个类和对象的元数据了:
letobj=newGuang("a",1);
letparamTypes=Reflect.getMetadata("design:paramtypes",obj,"add");
//[Number,Number]
这里我们用 Reflect.getMetadata 的 api 取出了 add 方法的参数的类型。
nest 的源码:
上面就是 @Module 装饰器的实现,里面就调用了 Reflect.defineMetadata 来给这个类添加了一些元数据。
所以我们这样用的时候:
import{Module}from'@nestjs/common';
import{CatsController}from'./cats.controller';
import{CatsService}from'./cats.service';
@Module({
controllers:[CatsController],
providers:[CatsService],
})
exportclassCatsModule{}
其实就是给 CatsModule 添加了 controllers 的元数据和 providers 的元数据。
后面创建 IOC 容器的时候就会取出这些元数据来处理:
而且 @Controller 和 @Injectable 的装饰器也是这样实现的:
看到这里,大家是否想明白 nest 的实现原理了呢?
其实就是通过装饰器给 class 或者对象添加元数据,然后初始化的时候取出这些元数据,进行依赖的分析,然后创建对应的实例对象就可以了。
所以说,nest 实现的核心就是 Reflect metadata 的 api。
当然,现在 metadata 的 api 还在草案阶段,需要使用 reflect-metadata 这个 polyfill 包才行。
其实还有一个疑问,依赖的扫描可以通过 metadata 数据,但是创建的对象需要知道构造器的参数,现在并没有添加这部分 metadata 数据呀:
比如这个 CatsController 依赖了 CatsService,但是并没有添加 metadata 呀:
import{Body,Controller,Get,Param,Post}from'@nestjs/common';
import{CatsService}from'./cats.service';
import{CreateCatDto}from'./dto/create-cat.dto';
@Controller('cats')
exportclassCatsController{
constructor(privatereadonlycatsService:CatsService){}
@Post()
asynccreate(@Body()createCatDto:CreateCatDto){
this.catsService.create(createCatDto);
}
@Get()
asyncfindAll():Promise<Cat[]>{
returnthis.catsService.findAll();
}
}
这就不得不提到 TypeScript 的优势了,TypeScript 支持编译时自动添加一些 metadata 数据:
比如这段代码:
import"reflect-metadata";
classGuang{
@Reflect.metadata("名字","光光")
publicsay(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,然后运行的时候通过这些元数据来实现依赖的扫描,对象的创建等等功能。
reflect metadata Nest实现原理是什么的详细内容,希望对您有所帮助,信息来源于网络。