Nest 的實現(xiàn)原理?理解了 Reflect Metadata 就懂了
Nest 是 Node.js 的服務(wù)端框架,它最出名的就是 IOC(inverse of control) 機制了,也就是不需要手動創(chuàng)建實例,框架會自動掃描需要加載的類,并創(chuàng)建他們的實例放到容器里,實例化時還會根據(jù)該類的構(gòu)造器參數(shù)自動注入依賴。
它一般是這樣用的:
比如入口 Module 里引入某個模塊的 Module:
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
({
imports: [CatsModule],
})
export class AppModule {}
然后這個模塊的 Module 里會聲明 Controller 和 Service:
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
Controller 里就是聲明 url 對應(yīng)的處理邏輯:
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
()
async create( () createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
這個 CatsController 的構(gòu)造器聲明了對 CatsService 的依賴:
然后 CatsService 里就可以去操作數(shù)據(jù)庫進(jìn)行增刪改查了。這里簡單實現(xiàn)一下:
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
之后在入口處調(diào)用 create 把整個 nest 應(yīng)用跑起來:
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 對應(yīng)的 url,打個斷點:
你會發(fā)現(xiàn) controller 的實例已經(jīng)創(chuàng)建了,而且 service 也給注入了。這就是依賴注入的含義。
這種機制就叫做 IOC(控制反轉(zhuǎn)),也叫依賴注入,好處是顯而易見的,就是只需要聲明依賴關(guān)系,不需要自己創(chuàng)建對象,框架會掃描聲明然后自動創(chuàng)建并注入依賴。
Java 里最流行的 Spring 框架就是 IOC 的實現(xiàn),而 Nest 也是這樣一個實現(xiàn)了 IOC 機制的 Node.js 的后端框架。
不知道大家有沒有感覺很神奇,只是通過裝飾器聲明了一下,然后啟動 Nest 應(yīng)用,這時候?qū)ο缶徒o創(chuàng)建好了,依賴也給注入了。
那它是怎么實現(xiàn)的呢?
大家如果就這樣去思考它的實現(xiàn)原理,還真不一定能想出來,因為缺少了一些前置知識。也就是實現(xiàn) Nest 最核心的一些 api:Reflect 的 metadata 的 api。
Reflect Metadata
有的同學(xué)會說,Reflect 的 api 我很熟呀,就是操作對象的屬性、方法、構(gòu)造器的一些 api:
比如 Reflect.get 是獲取對象屬性值。
Reflect.set 是設(shè)置對象屬性值。
Reflect.has 是判斷對象屬性是否存在
Reflect.apply 是調(diào)用某個方法,傳入對象和參數(shù)。
Reflect.construct 是用構(gòu)造器創(chuàng)建對象實例,傳入構(gòu)造器參數(shù)。
這些 api 在 MDN 文檔里可以查到,因為它們都已經(jīng)是 es 標(biāo)準(zhǔn)了,也被很多瀏覽器實現(xiàn)了。
但是實現(xiàn) Nest 用到的 api 還沒有進(jìn)入標(biāo)準(zhǔn),還在草案階段,也就是 metadata 的 api:
它有這些 api:
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 分別用于設(shè)置和獲取某個類的元數(shù)據(jù),如果最后傳入了屬性名,還可以單獨為某個屬性設(shè)置元數(shù)據(jù)。
那元數(shù)據(jù)存在哪呢?
存在類或者對象上呀,如果給類或者類的靜態(tài)屬性添加元數(shù)據(jù),那就保存在類上,如果給實例屬性添加元數(shù)據(jù),那就保存在對象上,用類似 [[metadata]] 的 key 來存的。
這有啥用呢?
看上面的 api 確實看不出啥來,但它也支持裝飾器的方式使用:
metadata(metadataKey, metadataValue).
class C {
.metadata(metadataKey, metadataValue)
method() {
}
}
Reflect.metadata 裝飾器當(dāng)然也可以再封裝一層:
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);
}
(String, Number)
class Guang {
constructor(text, i) {
}
(String)
get name() { return "text"; }
(Function)
(Number, Number)
(Number)
add(x, y) {
return x + y;
}
}
然后我們就可以通過 Reflect metadata 的 api 或者這個類和對象的元數(shù)據(jù)了:
let obj = new Guang("a", 1);
let paramTypes = Reflect.getMetadata("design:paramtypes", inst, "add");
// [Number, Number]
這里我們用 Reflect.getMetadata 的 api 取出了 add 方法的參數(shù)的類型。
看到這里,大家是否明白 nest 的原理了呢?
我們再看下 nest 的源碼:
上面就是 @Module 裝飾器的實現(xiàn),里面就調(diào)用了 Reflect.defineMetadata 來給這個類添加了一些元數(shù)據(jù)。
所以我們這樣用的時候:
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
其實就是給 CatsModule 添加了 controllers 的元數(shù)據(jù)和 providers 的元數(shù)據(jù)。
后面創(chuàng)建 IOC 容器的時候就會取出這些元數(shù)據(jù)來處理:
而且 @Controller 和 @Injectable 的裝飾器也是這樣實現(xiàn)的:
看到這里,大家是否想明白 nest 的實現(xiàn)原理了呢?
其實就是通過裝飾器給 class 或者對象添加元數(shù)據(jù),然后初始化的時候取出這些元數(shù)據(jù),進(jìn)行依賴的分析,然后創(chuàng)建對應(yīng)的實例對象就可以了。
所以說,nest 實現(xiàn)的核心就是 Reflect metadata 的 api。
當(dāng)然,現(xiàn)在 metadata 的 api 還在草案階段,需要使用 reflect-metadata 這個 polyfill 包才行。
其實還有一個疑問,依賴的掃描可以通過 metadata 數(shù)據(jù),但是創(chuàng)建的對象需要知道構(gòu)造器的參數(shù),現(xiàn)在并沒有添加這部分 metadata 數(shù)據(jù)呀:
比如這個 CatsController 依賴了 CatsService,但是并沒有添加 metadata 呀:
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
()
async create( () createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
這就不得不提到 TypeScript 的優(yōu)勢了,TypeScript 支持編譯時自動添加一些 metadata 數(shù)據(jù):
比如這段代碼:
import "reflect-metadata";
class Guang {
.metadata("名字", "光光")
public say(a: number): string {
return '加油鴨';
}
}
按理說我們只添加了一個元數(shù)據(jù),生成的代碼也確實是這樣的:
但是呢,ts 有一個編譯選項叫做 emitDecoratorMetadata,開啟它就會自動添加一些元數(shù)據(jù)。
開啟之后再試一下:
你會看到多了三個元數(shù)據(jù):
design:type 是 Function,很明顯,這個是描述裝飾目標(biāo)的元數(shù)據(jù),這里裝飾的是函數(shù)
design:paramtypes 是 [Number],很容易理解,就是參數(shù)的類型
design:returntype 是 String,也很容易理解,就是返回值的類型
所以說,只要開啟了這個編譯選項,ts 生成的代碼會自動添加一些元數(shù)據(jù)。
然后創(chuàng)建對象的時候就可以通過 design:paramtypes 來拿到構(gòu)造器參數(shù)的類型了,那不就知道怎么注入依賴了么?
所以,nest 源碼里你會看到這樣的代碼:
就是獲取構(gòu)造器的參數(shù)類型的。這個常量就是我們上面說的那個:
這也是為什么 nest 會用 ts 來寫,因為它很依賴這個 emitDecoratorMetadata 的編譯選項。
你用 cli 生成的代碼模版里也都默認(rèn)開啟了這個編譯選項:
這就是 nest 的核心實現(xiàn)原理:通過裝飾器給 class 或者對象添加 metadata,并且開啟 ts 的 emitDecoratorMetadata 來自動添加類型相關(guān)的 metadata,然后運行的時候通過這些元數(shù)據(jù)來實現(xiàn)依賴的掃描,對象的創(chuàng)建等等功能。
總結(jié)
Nest 是 Node.js 的后端框架,他的核心就是 IOC 容器,也就是自動掃描依賴,創(chuàng)建實例對象并且自動依賴注入。
要搞懂它的實現(xiàn)原理,需要先學(xué)習(xí) Reflect metadata 的 api:
這個是給類或者對象添加 metadata 的。可以通過 Reflect.metadata 給類或者對象添加元數(shù)據(jù),之后用到這個類或者對象的時候,可以通過 Reflect.getMetadata 把它們?nèi)〕鰜怼?/p>
Nest 的 Controller、Module、Service 等等所有的裝飾器都是通過 Reflect.meatdata 給類或?qū)ο筇砑釉獢?shù)據(jù)的,然后初始化的時候取出來做依賴的掃描,實例化后放到 IOC 容器里。
實例化對象還需要構(gòu)造器參數(shù)的類型,這個開啟 ts 的 emitDecoratorMetadata 的編譯選項之后, ts 就會自動添加一些元數(shù)據(jù),也就是 design:type、design:paramtypes、design:returntype 這三個,分別代表被裝飾的目標(biāo)的類型、參數(shù)的類型、返回值的類型。
當(dāng)然,reflect metadata 的 api 還在草案階段,需要引入 refelect metadata 的包做 polyfill。
nest 的一系列裝飾器就是給 class 和對象添加 metadata 的,然后依賴掃描和依賴注入的時候就把 metadata 取出來做一些處理。
理解了 metadata,nest 的實現(xiàn)原理就很容易搞懂了。