nestjs系列文章二:《抽象!Reflect-Metadata-描述数据的数据?这次终于搞懂了》

396 阅读5分钟

前言

在学习nestjs的过程中,Reflect-Metadata很重要,因为nestjs主要是用装饰器来实现各种功能,装饰器可以通过声明的方法,在定义类的时候,扩充类成员和类成员的能力。

Reflect-Metadata与装饰器配合使用能够很方便的实现在框架开发中的如路由/控制器的注册和管理通过元数据来判断对某个方法或属性的访问权限对属性添加校验的功能。那么为什么要用元数据去做这些工作呢?

  1. 首先元数据可以在不用修改原始代码(这点很重要,不会污染源代码)的情况下,动态的决定某些动作,如通过元数据对方法进行权限控制。
  2. 其次Reflect-matadata可以很方便的在运行时操作,如标记类的构造函数参数或属性,运行时会自动解析并注入依赖。
  3. API统一,由reflect-metadata统一提供。
  4. 与装饰器配合使用更加强大, 1 + 1 > 2。
  5. Reflect-Metadata是 NestJS 实现依赖注入、装饰器的核心,存取数据很多都是通过它。

Reflect-Metadata作为ECMA的提案(还没有实现纳入标准),但是Typescript早已支持了Reflect-Metadata相关的功能。再看例子之前,先要明白一个概念,什么是元数据? 元数据其实就是描述数据的数据

提案介绍: rbuckton.github.io/reflect-met…

github: github.com/rbuckton/re…

定义元数据

定义元数据的方式有两种

  1. 装饰器
  2. 使用API定义的方法

api:

function defineMetadata(metadataKey: any, metadataValue: any, target: Object): void;
function defineMetadata(metadataKey: any, metadataValue: any, target: Object, propertyKey: string | symbol): void;

代码:

import 'reflect-metadata'class AppController {
  
  // 1. 使用装饰器的方式
  @Reflect.metadata('money', {moneny: 10000})
  private userName: string
  constructor(userName: string) {
    this.userName = userName
  }
}
​
const appController = new AppController('zhangsan');
​
// 2. 使用api
// 给userName定义元数据
Reflect.defineMetadata('xingge','kailang',appController,'userName');
​
export default AppController

查看元数据

查看元数据是否存在

api:

function hasMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;

代码:

// 查看userName是否存在元数据
const hasUserNameMetadata1 = Reflect.hasMetadata('xingge',appController,'userName')
const hasUserNameMetadata2 = Reflect.hasMetadata('money',appController,'userName')
console.log(hasUserNameMetadata1); // true
console.log(hasUserNameMetadata2); // true

获取元数据

api:

function getMetadata(metadataKey: any, target: Object): any;
function getMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): any;

代码:

// 获取userName元数据
const userNameMetaValue1 = Reflect.getMetadata('xingge',appController,'userName')
const userNameMetaValue2 = Reflect.getMetadata('money',appController,'userName')
console.log("userNameMetaValue1: ", userNameMetaValue1); // kailang
console.log("userNameMetaValue2: ", userNameMetaValue2); // { moneny: 10000 }

删除元数据

api:

function deleteMetadata(metadataKey: any, target: Object): boolean;
function deleteMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;

代码:

const isDeleteSuccess1 = Reflect.deleteMetadata('xingge',appController,'userName')
const isDeleteSuccess2 = Reflect.deleteMetadata('aaaaa',appController,'userName')
console.log(isDeleteSuccess1); // true
console.log(isDeleteSuccess2); // false

获取对象上所有的元数据键

api:

function getMetadataKeys(target: Object): any[];
function getMetadataKeys(target: Object, propertyKey: string | symbol): any[];

代码:

const keys = Reflect.getMetadataKeys(appController, 'userName')
console.log(keys); // [ 'xingge', 'money' ]

还有两个api hasOwnMetadatagetOwnMetadata

这两个方法在nest中不常用,nestjs源码中也没有用到。

Reflect.getOwnMetadata() 方法只在对象本身上查找元数据。它不会沿着原型链向上搜索。

Reflect.getMetadata() 方法会首先在目标对象上查找元数据,如果没有找到,则会继续沿着原型链向上搜索,直到找到为止。如果在原型链上找到了元数据,就会返回该元数据。如果没找到则返回undefined。

定义一个UserController类继承AppController

class UserController extends AppController {
  constructor(userName: string) {
    super(userName)
  }
}

getHello定义元数据

class AppController {
  @Reflect.metadata('money', {moneny: 10000})
  private userName: string
  constructor(userName: string) {
    this.userName = userName
  }
​
  // 给getHello定义元数据
  @Reflect.metadata('helloKey','helloValue')
  getHello(): string { 
    return 'Hello World!'
  }
}

分别从appControlleruserController获取getHello的元数据

console.log(Reflect.getMetadata('helloKey', appController, 'getHello'));  // helloValue
console.log(Reflect.getOwnMetadata('helloKey', appController, 'getHello')); // undefinedconst userController = new UserController('lisi');  
console.log(Reflect.getMetadata('helloKey', userController, 'getHello')); // helloValue

因为helloKey是定义在父级的,getOwnMetadata获取不到,而appControlleruserControllergetMetadata方法都可以获取到,因为它可以沿着原型链查找。

如果非要通过getOwnMetadata获取数据呢,那就从原型上获取

console.log(Reflect.getMetadata('helloKey', appController, 'getHello'));  // helloValue
// 获取到 AppController 的 prototype
console.log(Reflect.getOwnMetadata('helloKey', Reflect.getPrototypeOf(appController)!, 'getHello')); // helloValueconst userController = new UserController('lisi');  
console.log(Reflect.getMetadata('helloKey', userController, 'getHello')); // helloValue
// @ts-ignore
// 获取到UserController的原型的原型
console.log(Reflect.getOwnMetadata('helloKey', UserController.__proto__.prototype, 'getHello')); // helloValue

hasOwnMetadata方法同理。

其实Reflec-metadata还有一个API,metadata

function metadata(metadataKey: any, metadataValue: any)

其实这个api我们已经用过了,是在类中使用的。

image-20240706233832775

emitDecoratorMetadata

这个tsconfig配置项与Reflect-matadata息息相关,用来决定是否启用 编译结果是否输出元数据类型用来做反射。

ts代码

import "reflect-metadata";
​
function LogMethod(
  target: any,
  propertyKey: string | symbol,
  descriptor: PropertyDescriptor
) {
  console.log(target);
  console.log(propertyKey);
  console.log(descriptor);
}
 
class Demo {
  @LogMethod
  public foo(bar: number) {
    // do nothing
  }
}
 
const demo = new Demo();

开启emitDecoratorMetadata编译后的结果

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
function LogMethod(target, propertyKey, descriptor) {
    console.log(target);
    console.log(propertyKey);
    console.log(descriptor);
}
class Demo {
    foo(bar) {
        // do nothing
    }
}
__decorate([
    LogMethod,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number]),
    __metadata("design:returntype", void 0)
], Demo.prototype, "foo", null);
const demo = new Demo();

多出来的这几行代码(ts把这三个键和对应的类型信息添加到数据上)

__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", void 0)
  • design:type 表示被装饰的对象是什么类型
  • design:paramtypes 表示被装饰对象的参数类型, 是一个表示类型的数组, 如果不是函数, 则没有该 key
  • design:returntype 表示被装饰对象的返回值属性

实现依赖注入

我们了解了design:xxx的意思后,结合reflect-metadata和装饰器,就可以实现依赖注入了

import "reflect-metadata";
​
function Injectable() {
  return function (target: any) {
    Reflect.defineMetadata("injectable", true, target);
  };
}
​
class MyDependency {
  doSomething() {
    console.log("MyDependency doSomething执行");
  }
}
​
class MyDependency1 {
  dep2Method() {
    console.log("MyDependency1 dep2Method 执行");
  }
}
​
​
@Injectable()
class MyService {
  constructor(private _dependency: MyDependency, private _dependency1: MyDependency1) {}
​
  doSomething() {
    this._dependency.doSomething();
    this._dependency1.dep2Method();
  }
}
​
​
class DependencyInjection {
  static get<T>(target: any): T {
    const isInjectable = Reflect.getMetadata("injectable", target);
    
    if (!isInjectable) {
      throw new Error("Target is not injectable");
    }
​
    const dependencies = Reflect.getMetadata("design:paramtypes", target) || []; // [ [class MyDependency], [class MyDependency1] ]
    
   const instances = dependencies.map(provider => new provider());
​
    return new target(...instances);
  }
}
​
const myService = DependencyInjection.get<MyService>(MyService);
/**
MyDependency doSomething执行
MyDependency1 dep2Method 执行
 */
myService.doSomething(); 

上面使用@Injectable装饰器进行依赖注入,不手动实例化对象,也可以使用this._dependency获取到实例对象。

总结

通过上文我们了解了Reflect-Metadata的作用和其API。还有一些设计模式相关的东西。

Reflect-Metadata给js通过了强大的元编程的能力,而且在Nestjs源码中路由、控制器这些也是通过 Reflect-Metadata 进行灵活的配置和管理的。