// 要引入这个库
import “reflect-metadata”;
@Reflect.metadata(‘name’, ‘Person’)
class Person {
@Reflect.metadata(‘words’, ‘hello world’)
public speak(): string {
return ‘hello world’
}
}
Reflect.getMetadata(‘name’, Person) // ‘Person’
Reflect.getMetadata(‘words’, new Person(), ‘speak’) // ‘hello world’
// 这里为什么要用 new A(),用 A 不行么?后文会讲到
是不是很简单?对照这个例子,我们再引出 Metadata 的四个概念:
-
Metadata Key {Any}(后文简写 k)。元数据的 Key,对于一个对象来说,它可以有很多元数据,每一个元数据都对应有一个 Key。一个很简单的例子就是说,你可以在一个对象上面设置一个叫做 ‘name’ 的 Key 用来设置他的名字,用一个 ‘created time’ 的 Key 来表示他创建的时间。这个 Key 可以是任意类型。在后面会讲到内部本质就是一个 Map 对象。
-
Metadata Value {Any} (后文简写 v)。元数据的类型,任意类型都行。
-
Target {Object} (后文简写 o)。表示要在这个对象上面添加元数据
-
Property {String|Symbol} (后文简写 p)。用于设置在哪个属性上添加元数据。大家可能会想,这个是干什么用的,不是可以在对象上面添加元数据了么?其实不仅仅可以在对象上面添加元数据,甚至还可以在对象的属性上面添加元数据。其实大家可以这样理解,当你给一个对象定义元数据的时候,相当于你是默认指定了 undefined 作为 Property。
了解了这 4 个概念,我们阅读对应的 API 就会比较容易。
API设计
最为全面的 API 可以查阅标准文档: https://rbuckton.github.io/reflect-metadata/
API 的设计也是非常清晰明了,metadata 毕竟也属于 “数据”,那么对应的 API 就是跟数据库的 CURD 增删改查的操作相对应的。
先罗列一下完整的 API 声明,然后我们在挨个拆分讲解:
namespace Reflect {
// 用于装饰器
metadata(k, v): (target, property?) => void
// 在对象上面定义元数据
defineMetadata(k, v, o, p?): void
// 是否存在元数据
hasMetadata(k, o, p?): boolean
hasOwnMetadata(k, o, p?): boolean
// 获取元数据
getMetadata(k, o, p?): any
getOwnMetadata(k, o, p?): any
// 获取所有元数据的 Key
getMetadataKeys(o, p?): any[]
getOwnMetadataKeys(o, p?): any[]
// 删除元数据
deleteMetadata(k, o, p?): boolean
}
▐ 创建元数据**(Reflect.metadata/Reflect.defineMetadata)**
两种方式创建,其最本质都是调用源码中 OrdinaryDefineOwnMetadata 方法,所以结果是一样的,只是使用方式不一样。
一种是通过装饰器声明方式创建,推荐的方式,也是很主流的一种方式:
// 装饰类
@Reflect.metadata(key, value)
class Example {
}
// 装饰静态属性
class Example {
@Reflect.metadata(key, value)
static staticProperty;
}
// 装饰实例属性
class Example {
@Reflect.metadata(key, value)
property;
}
// 装饰静态方法
class Example {
@Reflect.metadata(key, value)
static staticMethod() { }
}
// 装饰实例方法
class Example {
@Reflect.metadata(key, value)
method() { }
}
另一种就是 “事后”(类创建完后)再给目标对象创建元数据:
class Example {
// property declarations are not part of ES6, though they are valid in TypeScript:
// static staticProperty;
// property;
constructor§ { }
static staticMethod§ { }
method§ { }
}
// 给 类本身 加元数据
Reflect.defineMetadata(“custom:annotation”, options, Example);
// 给 类的静态属性 新增元数据
Reflect.defineMetadata(“custom:annotation”, options, Example, “staticProperty”);
// 给 类的实例属性 新增元数据,注意是加在 prototype 上
Reflect.defineMetadata(“custom:annotation”, options, Example.prototype, “property”);
// 给 类的静态方法 新增元数据
Reflect.defineMetadata(“custom:annotation”, options, Example, “staticMethod”);
// 给 类的实例方法 新增元数据,注意是加在 prototype 上
Reflect.defineMetadata(“custom:annotation”, options, Example.prototype, “method”);
这两种方式之间的联系就是,装饰器方法 Reflect.metadata 其实内部就是用 Reflect.defineMetadata 实现的:
function metadata(metadataKey: any, metadataValue: any): Decorator {
return (target, key?) => Reflect.defineMetadata(“custom:annotation”, options, target, key);
}
▐ 查询****元数据
这里的 “查询” 是加引号的,因为元数据的查询的功能很简单,只要该元数据即可,不需获取要写 SQL 那么复杂。由于涉及到原型链,所以每个 API 都存在一对,用以区分是否包含原型链的操作。
- 获取元数据(getMetadata/getOwnMetadata)
这里涉及到两个相关的 API,getMetadata/getOwnMetadata 两个方法,它们之间的区别前者会包含原型链查找,后者不会查找原型链。
(类似于 Object.hasProperty 和 Object.hasOwnProperty 这两个方法的区别)
举例如下:
class A {
@Reflect.metadata(‘name’, ‘hello’)
hello() {}
}
const t1 = new A()
const t2 = new A()
// 给 t2.hello 新增元数据内容 otherName -> world
// 此时会新增到 t2.prototype(原型链) 上(注意不是 A.prototype 上)
Reflect.defineMetadata(‘otherName’, ‘world’, t2, ‘hello’);
// 首先,t1/t2 自己能获取原本的原型链 name 对应的元数据
Reflect.getMetadata(‘name’, t1, ‘hello’) // ‘hello’
Reflect.getMetadata(‘name’, t2, ‘hello’) // ‘hello’
// t1 自身上没定义 otherName 的元数据
Reflect.getMetadata(‘otherName’, t1, ‘hello’) // undefined
// t2 自身定义了 otherName 的元数据
Reflect.getMetadata(‘otherName’, t2, ‘hello’) // ‘world’
// t2 自身上只有 otherName 这个元数据,没有 name 元数据(该元数据来自父级)
Reflect.getOwnMetadata(‘name’, t2, ‘hello’) // undefined
Reflect.getOwnMetadata(‘otherName’, t2, ‘hello’) // ‘world’
- 判断是否存在元数据(hasMetadata/hasOwnMetadata)
同样的涉及到两个相关的 API,hasMetadata/hasOwnMetadata 两个方法,它们两者调用方式一样,唯一的区别是前者会包含原型链查找,后者不会查找原型链。而且入参含义跟getMetadata/getOwnMetadata 两个方法是一样的:
class Example {
// property declarations are not part of ES6, though they are valid in TypeScript:
// static staticProperty;
// property;
constructor§ { }
static staticMethod§ { }
method§ { }
}
// 类本身 上是否存在元数据
result = Reflect.hasMetadata(“custom:annotation”, options, Example);
// 类的静态属性 上是否存在元数据
result = Reflect.hasMetadata(“custom:annotation”, options, Example, “staticProperty”);
// 类的实例属性 上是否存在元数据,注意是在 prototype 上
result = Reflect.hasMetadata(“custom:annotation”, options, Example.prototype, “property”);
// 类的静态方法 上是否存在元数据
result = Reflect.hasMetadata(“custom:annotation”, options, Example, “staticMethod”);
// 类的实例方法 上是否存在元数据,注意是在 prototype 上
result = Reflect.hasMetadata(“custom:annotation”, options, Example.prototype, “method”);
- 获取 metaKeys(getMetadataKeys/getOwnMetadataKeys)
同样的涉及到两个相关的 API,getMetadataKeys/getOwnMetadataKeys 两个方法。
class Example {
// property declarations are not part of ES6, though they are valid in TypeScript:
// static staticProperty;
// property;
constructor§ { }
static staticMethod§ { }
method§ { }
}
// 获取 类本身 的元数据 keys
result = Reflect.getMetadataKeys(Example);
// 获取 类的静态属性 的元数据 keys
result = Reflect.getMetadataKeys(Example, “staticProperty”);
// 获取 类的实例属性 的元数据 keys,注意是从 prototype 上获取
result = Reflect.getMetadataKeys(Example.prototype, “property”);
// 获取 类的静态方法 的元数据 keys
result = Reflect.getMetadataKeys(Example, “staticMethod”);
// 获取 类的实例方法 的元数据 keys,注意是从 prototype 上获取
result = Reflect.getMetadataKeys(Example.prototype, “method”);
▐ 删除****元数据
用于删除元数据的 API 是 deleteMetadata,如果对象上有该元数据, 则会删除成功返回 true, 否则返回 false。因为比较简单, 就直接上例子了:
const type = ‘type’
@Reflect.metadata(type, ‘class’)
class DeleteMetadata {
@Reflect.metadata(type, ‘static’)
static staticMethod() {}
}
const res1 = Reflect.deleteMetadata(type, DeleteMetadata)
const res2 = Reflect.deleteMetadata(type, DeleteMetadata, ‘staticMethod’)
const res3 = Reflect.deleteMetadata(type, DeleteMetadata)
console.log(res1, res2, res3) // true true false
(注意并没有 deleteOwnMetadata 这样的 API)
TS福利:design:类型元数据
这里需要提及一点的是,在 TS 中的 reflect-metadata 是经过扩展[5]的(也就是说功能更加强劲),我们看个例子:
import “reflect-metadata”;
class Foo {
@Reflect.metadata(“hello”, “world”)
public say(a: string): string {
return ‘foo’;
}
}
我们查看其转换出来的 JS 代码,你会发现处理我们自己的元数据 “hello”->“world” 之外,还会额外给我们添加 “design:type”、“design:paramtypes” 和 “design:returntype” 这 3 个类型相关的元数据。
之所以会有,这是因为我们在 TS 中开启了 emitDecoratorMetadata(https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata)编译选项,这样 TS 在编译的时候会将类型元数据自动添加上去。这也是 TS 强类型编程带来的额外好处 —— 换言之如果你用 ES6 编程,需要自己加这 3 个元数据。
(当然,如果你关闭 emitDecoratorMetadata选项,也就没有这 3 个类型元数据了)
design:xxx 的内容分别表示什么意思呢[4]?
-
design:type 表示被装饰的对象是什么类型, 比如是字符串? 数字? 还是函数等
-
design:paramtypes 表示被装饰对象的参数类型, 是一个表示类型的数组, 如果不是函数, 则没有该 key
-
design:returntype 表示被装饰对象的返回值属性, 比如字符串,数字或函数等
我们看个例子[4]:
@Reflect.metadata(‘type’, ‘class’)
class A {
constructor(public name: string, public age: number) {
}
@Reflect.metadata(undefined, undefined)
method():boolean {
return true
}
}
const t1 = Reflect.getMetadata(‘design:paramtypes’, A)
const t2 = Reflect.getMetadata(‘design:returntype’, A.prototype, ‘method’)
const t3 = Reflect.getMetadata(‘design:type’, A.prototype, ‘method’)
console.log(…t1, t2, t3)
打印输出为:
但是要注意:
-
没有装饰的 target 是 get 不到这些 metadata 的
-
必须手动指定类型, 否则无法进行推断。比如 method 方法如果不指定返回值为 boolean, 那么 t2 将会是 undefined
-
应用的顺序为 type -> paramtypes -> returntype
按理说本文到此就结束了,如果想看 reflect-metadata 源码是如何的,可以继续阅读下去。
进阶:reflect-metadata源码设计解读
我还是比较推荐阅读源码的,何况 reflect-metadata 的源码(地址:https://github.com/rbuckton/reflect-metadata/blob/master/Reflect.ts)不算多,咋一看有 1.8k 多行,可真正看下来核心源码没几行的。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
由于细节内容实在太多了,为了不影响文章的观赏性,只截出了一部分知识点大致的介绍一下,每个小节点里面都有更细化的内容!
小编准备了一份Java进阶学习路线图(Xmind)以及来年金三银四必备的一份《Java面试必备指南》
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
mg-edAIFg0w-1713703863147)]
[外链图片转存中…(img-cCVwlfk3-1713703863148)]
[外链图片转存中…(img-Xq5TS87s-1713703863148)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
由于细节内容实在太多了,为了不影响文章的观赏性,只截出了一部分知识点大致的介绍一下,每个小节点里面都有更细化的内容!
[外链图片转存中…(img-AvWg1B9c-1713703863148)]
小编准备了一份Java进阶学习路线图(Xmind)以及来年金三银四必备的一份《Java面试必备指南》
[外链图片转存中…(img-0ZkoSbwy-1713703863149)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!