【NestJS】Reflect Metadata 全解
🧩 一、Reflect Metadata 是什么?
元数据(metadata)就是 ——
“附加在类、方法、参数、属性等上的隐藏信息”。
这些信息不会改变代码逻辑,
但可以被框架(比如 Nest)在后期拿出来“做事”。
举个简单例子:
class UserController {@Get('users')getUsers() {}
}
这里的 @Get('users') 本质上不是注册接口,
只是调用了一个函数,在这个方法上打了标记:
Reflect.defineMetadata('path', 'users', target.getUsers);
Reflect.defineMetadata('method', 'GET', target.getUsers);
然后 Nest 启动时再通过:
Reflect.getMetadata('path', target.getUsers);
Reflect.getMetadata('method', target.getUsers);
去拿出这些元数据,再真正注册到路由系统里。
⚙️ 二、Reflect.defineMetadata() 与 Reflect.getMetadata() 用法
✅ 定义元数据
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey?);
- metadataKey:字符串或 Symbol,用来标识数据。
- metadataValue:要保存的任意值(可以是对象、数组、字符串等)。
- target:要绑定的目标(类、方法、属性)。
- propertyKey(可选):要绑定到哪个属性或方法上。
✅ 读取元数据
Reflect.getMetadata(metadataKey, target, propertyKey?);
- 参数同上,读取之前写入的元数据。
🧠 三、最小示例理解
import 'reflect-metadata'; // 必须引入一次class MyClass {}// 定义元数据
Reflect.defineMetadata('role', 'admin', MyClass);// 读取元数据
console.log(Reflect.getMetadata('role', MyClass)); // 输出 'admin'
这说明你可以把一些“额外信息”藏到类或函数上。
🔍 四、在方法或属性上使用
class UserService {@Reflect.metadata('cache', true)getUsers() {}
}// 等价于:
Reflect.defineMetadata('cache', true, UserService.prototype, 'getUsers');// 获取时:
const value = Reflect.getMetadata('cache', UserService.prototype, 'getUsers');
console.log(value); // true
🧩 五、嵌套用法(例如在装饰器中)
通常装饰器都是利用这两函数在背后干活:
function MyDecorator(path: string) {return (target: any, key: string, descriptor: PropertyDescriptor) => {Reflect.defineMetadata('path', path, descriptor.value);};
}class MyController {@MyDecorator('/users')getUsers() {}
}console.log(Reflect.getMetadata('path', MyController.prototype.getUsers)); // '/users'
这就是 Nest @Get() 的简化版。
🧩 六、组合使用(Controller + Method)
这也是 NestJS 路由系统的经典套路 👇
// Controller 装饰器
function Controller(prefix: string) {return (target: any) => {Reflect.defineMetadata('prefix', prefix, target);};
}// Method 装饰器
function Get(path: string) {return (target: any, key: string, descriptor: PropertyDescriptor) => {Reflect.defineMetadata('path', path, descriptor.value);Reflect.defineMetadata('method', 'GET', descriptor.value);};
}@Controller('users')
class UserController {@Get(':id')getUser() {}
}// Nest 启动阶段读取:
const prefix = Reflect.getMetadata('prefix', UserController);
const path = Reflect.getMetadata('path', UserController.prototype.getUser);
const method = Reflect.getMetadata('method', UserController.prototype.getUser);console.log(`${method} /${prefix}/${path}`); // GET /users/:id
🧩 七、Reflect.getOwnMetadata() vs getMetadata()
| 方法 | 是否会查原型链 | 典型用途 |
|---|---|---|
getMetadata() | ✅ 会沿原型链向上查找 | 常用于装饰器系统(如继承时) |
getOwnMetadata() | ❌ 只查当前对象 | 常用于判断当前类是否定义过 |
🧩 八、Reflect.hasMetadata()、Reflect.deleteMetadata()
| 方法 | 作用 |
|---|---|
Reflect.hasMetadata(key, target, propertyKey?) | 判断是否存在该元数据(包含原型链) |
Reflect.hasOwnMetadata(key, target, propertyKey?) | 只查自己 |
Reflect.deleteMetadata(key, target, propertyKey?) | 删除定义的元数据 |
示例:
Reflect.hasMetadata('path', MyController.prototype.getUser); // true
Reflect.deleteMetadata('path', MyController.prototype.getUser);
Reflect.hasMetadata('path', MyController.prototype.getUser); // false
🧩 九、真实 Nest 内部例子
Nest 的 @Controller()、@Get() 等装饰器底层其实就是这样写的:
export const PATH_METADATA = 'path';
export const METHOD_METADATA = 'method';export function Controller(path: string): ClassDecorator {return (target) => {Reflect.defineMetadata(PATH_METADATA, path, target);};
}export function Get(path: string): MethodDecorator {return (target, key, descriptor) => {Reflect.defineMetadata(PATH_METADATA, path, descriptor.value);Reflect.defineMetadata(METHOD_METADATA, 'GET', descriptor.value);};
}
Nest 在启动时读取:
const controllerPath = Reflect.getMetadata(PATH_METADATA, ControllerClass);
const methodPath = Reflect.getMetadata(PATH_METADATA, methodHandler);
const httpMethod = Reflect.getMetadata(METHOD_METADATA, methodHandler);
然后注册成路由。
🧩 十、你可以自己实现一套小型 Nest 路由系统
import 'reflect-metadata';const PATH = 'path';
const METHOD = 'method';function Controller(prefix: string) {return (target: any) => Reflect.defineMetadata(PATH, prefix, target);
}function Route(method: string, path: string) {return (target: any, key: string, descriptor: PropertyDescriptor) => {Reflect.defineMetadata(PATH, path, descriptor.value);Reflect.defineMetadata(METHOD, method, descriptor.value);};
}const Get = (path: string) => Route('GET', path);
const Post = (path: string) => Route('POST', path);@Controller('users')
class UserController {@Get('/')getAll() {}@Post('/')create() {}
}// 启动阶段自动打印
for (const key of Object.getOwnPropertyNames(UserController.prototype)) {const handler = UserController.prototype[key];const method = Reflect.getMetadata(METHOD, handler);const path = Reflect.getMetadata(PATH, handler);if (method && path) {const prefix = Reflect.getMetadata(PATH, UserController);console.log(`[${method}] /${prefix}${path}`);}
}
输出:
[GET] /users/
[POST] /users/
这几乎就是 Nest 的控制器注册原理!
✅ 总结表
| 函数 | 作用 | 是否查原型链 |
|---|---|---|
defineMetadata(key, value, target, prop?) | 定义元数据 | 否 |
getMetadata(key, target, prop?) | 获取元数据 | ✅ 是 |
getOwnMetadata(key, target, prop?) | 获取自身定义的元数据 | ❌ 否 |
hasMetadata(key, target, prop?) | 是否存在(包含继承) | ✅ 是 |
hasOwnMetadata(key, target, prop?) | 是否存在(不含继承) | ❌ 否 |
deleteMetadata(key, target, prop?) | 删除元数据 | 否 |
