反射元数据(Reflect Metadata)的底层机制
在 NestJS 和其他现代 TypeScript 框架中,元数据(Metadata)扮演着至关重要的角色。通过 @SetMetadata('roles', ['admin']) 这样的装饰器,我们可以在运行时获取和使用编译时定义的信息。但这些元数据到底存储在哪里?如何读取?本文将深入探讨 Reflect Metadata 的底层机制。
1. Reflect Metadata 基础
1.1 什么是元数据?
元数据是关于数据的数据,它提供了有关对象、类、方法、属性或参数的附加信息:
typescript
// 没有元数据的普通类
class UserService {
findUser(id: string) {
return { id, name: 'John Doe' };
}
}
// 带有元数据的类
@SetMetadata('version', '1.0')
@SetMetadata('author', 'John Smith')
class EnhancedUserService {
@SetMetadata('operation', 'read')
findUser(
@SetMetadata('parameter:type', 'userId') id: string
) {
return { id, name: 'John Doe' };
}
}1.2 Reflect Metadata API
Reflect Metadata 是 ECMAScript 的一个提案,提供了操作元数据的标准 API:
typescript
// Reflect Metadata 核心 API
interface Reflect {
// 定义元数据
defineMetadata(metadataKey: any, metadataValue: any, target: Object): void;
defineMetadata(metadataKey: any, metadataValue: any, target: Object, propertyKey: string | symbol): void;
// 获取元数据
getMetadata(metadataKey: any, target: Object): any;
getMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): any;
// 检查元数据是否存在
hasMetadata(metadataKey: any, target: Object): boolean;
hasMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;
// 获取所有元数据键
getMetadataKeys(target: Object): any[];
getMetadataKeys(target: Object, propertyKey: string | symbol): any[];
// 删除元数据
deleteMetadata(metadataKey: any, target: Object): boolean;
deleteMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;
}2. 元数据存储机制
2.1 内部存储结构
typescript
// 简化的元数据存储实现
class MetadataStorage {
private metadataMap = new WeakMap<Object, Map<string | symbol | undefined, Map<any, any>>>();
defineMetadata(metadataKey: any, metadataValue: any, target: Object, propertyKey?: string | symbol): void {
// 获取或创建目标对象的元数据映射
if (!this.metadataMap.has(target)) {
this.metadataMap.set(target, new Map());
}
const targetMetadata = this.metadataMap.get(target);
// 获取或创建属性的元数据映射
if (!targetMetadata.has(propertyKey)) {
targetMetadata.set(propertyKey, new Map());
}
const propertyMetadata = targetMetadata.get(propertyKey);
// 存储元数据
propertyMetadata.set(metadataKey, metadataValue);
}
getMetadata(metadataKey: any, target: Object, propertyKey?: string | symbol): any {
if (!this.metadataMap.has(target)) {
return undefined;
}
const targetMetadata = this.metadataMap.get(target);
if (!targetMetadata.has(propertyKey)) {
return undefined;
}
const propertyMetadata = targetMetadata.get(propertyKey);
return propertyMetadata.get(metadataKey);
}
hasMetadata(metadataKey: any, target: Object, propertyKey?: string | symbol): boolean {
return this.getMetadata(metadataKey, target, propertyKey) !== undefined;
}
}
// 全局元数据存储实例
const globalMetadataStorage = new MetadataStorage();2.2 元数据键的组织
typescript
// 元数据键的命名空间管理
class MetadataKeyManager {
static readonly NAMESPACE = 'nestjs:';
static createKey(key: string): string {
return `${this.NAMESPACE}${key}`;
}
static isNestKey(key: string): boolean {
return key.startsWith(this.NAMESPACE);
}
}
// 使用命名空间的元数据键
const ROUTE_ARGS = MetadataKeyManager.createKey('route_args');
const GUARDS = MetadataKeyManager.createKey('guards');
const INTERCEPTORS = MetadataKeyManager.createKey('interceptors');
const PIPES = MetadataKeyManager.createKey('pipes');
// 定义元数据
Reflect.defineMetadata(ROUTES, ['/users', '/admin'], UserController);
Reflect.defineMetadata(GUARDS, [AuthGuard, RolesGuard], UserController, 'getUser');3. SetMetadata 装饰器实现
3.1 基础实现
typescript
// SetMetadata 装饰器实现
function SetMetadata(metadataKey: string, metadataValue: any): Decorator {
return function (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) {
if (propertyKey) {
// 方法或属性级别的元数据
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);
} else {
// 类级别的元数据
Reflect.defineMetadata(metadataKey, metadataValue, target);
}
};
}
// 使用示例
@SetMetadata('version', '1.0')
class ApiController {
@SetMetadata('roles', ['admin', 'user'])
@SetMetadata('permissions', ['read', 'write'])
getData() {
return { message: 'Hello World' };
}
}3.2 高级 SetMetadata 实现
typescript
// 支持合并的 SetMetadata
function SetMetadata(metadataKey: string, metadataValue: any, options?: {
merge?: boolean;
unique?: boolean
}): MethodDecorator & ClassDecorator {
return function (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) {
if (options?.merge) {
// 获取现有元数据
const existingMetadata = propertyKey
? Reflect.getMetadata(metadataKey, target, propertyKey)
: Reflect.getMetadata(metadataKey, target);
let newMetadata = metadataValue;
if (Array.isArray(existingMetadata) && Array.isArray(metadataValue)) {
newMetadata = options?.unique
? [...new Set([...existingMetadata, ...metadataValue])]
: [...existingMetadata, ...metadataValue];
}
if (propertyKey) {
Reflect.defineMetadata(metadataKey, newMetadata, target, propertyKey);
} else {
Reflect.defineMetadata(metadataKey, newMetadata, target);
}
} else {
// 覆盖现有元数据
if (propertyKey) {
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);
} else {
Reflect.defineMetadata(metadataKey, metadataValue, target);
}
}
};
}4. 元数据读取机制
4.1 基本读取
typescript
// 基本元数据读取
function readMetadata() {
// 读取类级别的元数据
const version = Reflect.getMetadata('version', ApiController);
console.log('Version:', version); // 输出: Version: 1.0
// 读取方法级别的元数据
const roles = Reflect.getMetadata('roles', ApiController.prototype, 'getData');
console.log('Roles:', roles); // 输出: Roles: ['admin', 'user']
const permissions = Reflect.getMetadata('permissions', ApiController.prototype, 'getData');
console.log('Permissions:', permissions); // 输出: Permissions: ['read', 'write']
}
// 检查元数据是否存在
function hasMetadata() {
const hasVersion = Reflect.hasMetadata('version', ApiController);
console.log('Has version:', hasVersion); // 输出: Has version: true
const hasCache = Reflect.hasMetadata('cache', ApiController);
console.log('Has cache:', hasCache); // 输出: Has cache: false
}4.2 元数据继承
typescript
// 元数据继承机制
class BaseController {
@SetMetadata('defaultRoles', ['user'])
baseMethod() {}
}
@SetMetadata('controllerRoles', ['admin'])
class UserController extends BaseController {
@SetMetadata('methodRoles', ['moderator'])
userMethod() {}
}
// 元数据继承读取
function readInheritedMetadata() {
// 子类继承父类的元数据
const defaultRoles = Reflect.getMetadata('defaultRoles', UserController.prototype, 'baseMethod');
console.log('Default roles:', defaultRoles); // 输出: Default roles: ['user']
// 子类自己的元数据
const controllerRoles = Reflect.getMetadata('controllerRoles', UserController);
console.log('Controller roles:', controllerRoles); // 输出: Controller roles: ['admin']
}4.3 元数据聚合读取
typescript
// 从原型链聚合元数据
class MetadataAggregator {
static getMetadataRecursive(metadataKey: string, target: any, propertyKey?: string | symbol): any[] {
const metadataCollection: any[] = [];
let currentTarget = target;
// 遍历原型链
while (currentTarget) {
const metadata = propertyKey
? Reflect.getMetadata(metadataKey, currentTarget, propertyKey)
: Reflect.getMetadata(metadataKey, currentTarget);
if (metadata !== undefined) {
metadataCollection.push(metadata);
}
// 移动到原型链的下一级
currentTarget = Object.getPrototypeOf(currentTarget);
}
return metadataCollection;
}
}
// 使用示例
const allRoles = MetadataAggregator.getMetadataRecursive('roles', UserController.prototype, 'userMethod');
console.log('All roles:', allRoles); // 输出所有层级的 roles 元数据5. Reflector 工具类
5.1 NestJS Reflector 实现
typescript
// 简化的 Reflector 实现
@Injectable()
export class Reflector {
get<T>(metadataKey: string, target: Type<any> | Function): T | undefined {
return Reflect.getMetadata(metadataKey, target);
}
getAll<T>(metadataKey: string, targets: (Type<any> | Function)[]): T[] {
return targets.map(target => this.get<T>(metadataKey, target)).filter(Boolean) as T[];
}
getAllAndMerge<T>(metadataKey: string, targets: (Type<any> | Function)[]): T {
const metadataCollection = this.getAll<T[]>(metadataKey, targets);
if (metadataCollection.length === 0) {
return [] as unknown as T;
}
if (metadataCollection.some(item => !Array.isArray(item))) {
return metadataCollection[0];
}
return metadataCollection.reduce((prev, curr) => prev.concat(curr), []);
}
getAllAndOverride<T>(metadataKey: string, targets: (Type<any> | Function)[]): T {
for (const target of targets) {
const metadata = this.get<T>(metadataKey, target);
if (metadata !== undefined) {
return metadata;
}
}
return undefined;
}
}5.2 Reflector 使用示例
typescript
// 在守卫中使用 Reflector
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// 获取路由处理程序和控制器上的角色元数据
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(), // 路由处理方法
context.getClass(), // 控制器类
]);
if (!requiredRoles || requiredRoles.length === 0) {
return true; // 没有角色要求,允许访问
}
const { user } = context.switchToHttp().getRequest();
if (!user) {
return false;
}
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
// 在控制器中使用
@SetMetadata('roles', ['admin'])
@Controller('users')
export class UserController {
@Get()
@SetMetadata('roles', ['user', 'admin'])
getAllUsers() {
return [];
}
@Delete(':id')
@SetMetadata('roles', ['admin'])
deleteUser(@Param('id') id: string) {
// 只有 admin 角色可以删除用户
}
}6. 性能优化和缓存
6.1 元数据缓存
typescript
// 带缓存的元数据读取
class CachedReflector {
private readonly cache = new Map<string, any>();
get<T>(metadataKey: string, target: Type<any> | Function): T | undefined {
const cacheKey = this.generateCacheKey(metadataKey, target);
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
const metadata = Reflect.getMetadata(metadataKey, target);
this.cache.set(cacheKey, metadata);
return metadata;
}
private generateCacheKey(metadataKey: string, target: Type<any> | Function): string {
return `${metadataKey}:${target.name || target.constructor?.name}`;
}
clearCache() {
this.cache.clear();
}
}6.2 元数据预加载
typescript
// 元数据预加载机制
class MetadataPreloader {
private preloaded = false;
async preloadModuleMetadata(module: Type<any>) {
if (this.preloaded) {
return;
}
// 预加载模块中所有控制器和服务的元数据
const moduleMetadata = Reflect.getMetadata(MODULE_METADATA, module);
if (moduleMetadata?.controllers) {
for (const controller of moduleMetadata.controllers) {
await this.preloadControllerMetadata(controller);
}
}
if (moduleMetadata?.providers) {
for (const provider of moduleMetadata.providers) {
await this.preloadProviderMetadata(provider);
}
}
this.preloaded = true;
}
private async preloadControllerMetadata(controller: Type<any>) {
// 预加载控制器元数据
const methods = Object.getOwnPropertyNames(controller.prototype)
.filter(name => name !== 'constructor');
for (const method of methods) {
// 触发元数据加载
Reflect.getMetadata('design:paramtypes', controller.prototype, method);
}
}
private async preloadProviderMetadata(provider: Type<any>) {
// 预加载提供者元数据
Reflect.getMetadata('design:paramtypes', provider);
}
}7. 实际应用场景
7.1 权限系统实现
typescript
// 权限装饰器
export const RequirePermissions = (...permissions: string[]) =>
SetMetadata('permissions', permissions);
export const RequireRoles = (...roles: string[]) =>
SetMetadata('roles', roles);
// 权限守卫
@Injectable()
export class PermissionGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// 获取所需权限
const requiredPermissions = this.reflector.getAllAndOverride<string[]>('permissions', [
context.getHandler(),
context.getClass(),
]);
// 获取所需角色
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if ((!requiredPermissions || requiredPermissions.length === 0) &&
(!requiredRoles || requiredRoles.length === 0)) {
return true; // 没有权限要求
}
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user) {
return false;
}
// 检查权限
const hasPermission = !requiredPermissions ||
requiredPermissions.every(perm => user.permissions?.includes(perm));
// 检查角色
const hasRole = !requiredRoles ||
requiredRoles.some(role => user.roles?.includes(role));
return hasPermission && hasRole;
}
}
// 使用示例
@Controller('admin')
@RequireRoles('admin')
export class AdminController {
@Get('users')
@RequirePermissions('user.read')
getAllUsers() {
return [];
}
@Post('users')
@RequirePermissions('user.create')
createUser(@Body() createUserDto: CreateUserDto) {
// 创建用户
}
}7.2 缓存系统实现
typescript
// 缓存装饰器
export const Cacheable = (ttl?: number) =>
SetMetadata('cache:enabled', true) &&
SetMetadata('cache:ttl', ttl || 60000);
export const CacheKey = (key: string) =>
SetMetadata('cache:key', key);
// 缓存拦截器
@Injectable()
export class CacheInterceptor implements NestInterceptor {
constructor(private cacheService: CacheService, private reflector: Reflector) {}
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
const isCacheable = this.reflector.get<boolean>('cache:enabled', context.getHandler());
if (!isCacheable) {
return next.handle();
}
const cacheKey = this.reflector.get<string>('cache:key', context.getHandler()) ||
this.generateDefaultKey(context);
const ttl = this.reflector.get<number>('cache:ttl', context.getHandler()) || 60000;
// 尝试从缓存获取
const cached = await this.cacheService.get(cacheKey);
if (cached) {
return of(cached);
}
// 执行实际处理并缓存结果
return next.handle().pipe(
tap(response => {
this.cacheService.set(cacheKey, response, ttl);
})
);
}
private generateDefaultKey(context: ExecutionContext): string {
const request = context.switchToHttp().getRequest();
return `${request.method}:${request.url}`;
}
}
// 使用示例
@Controller('users')
export class UserController {
@Get(':id')
@Cacheable(30000) // 30秒缓存
@CacheKey('user_detail')
getUser(@Param('id') id: string) {
return this.userService.findById(id);
}
}8. 总结
Reflect Metadata 的底层机制包括:
- 存储结构:使用 WeakMap 和嵌套 Map 存储元数据
- 键值管理:通过命名空间避免键冲突
- 装饰器实现:通过 Reflect.defineMetadata 定义元数据
- 读取机制:支持类级别和方法级别的元数据读取
- 继承支持:可以从原型链继承元数据
- 工具类封装:通过 Reflector 提供便捷的元数据操作
元数据的核心优势:
- 编译时定义,运行时使用:在编写代码时定义信息,在运行时使用
- 非侵入性:不影响原有代码逻辑
- 灵活扩展:可以定义任意的元数据信息
- 框架集成:与装饰器系统完美集成
通过深入理解 Reflect Metadata 机制,我们可以:
- 更好地使用 NestJS 的各种装饰器
- 创建自定义的元数据装饰器
- 实现复杂的运行时逻辑
- 构建更加灵活和可扩展的应用
在下一篇文章中,我们将探讨平台无关性:Nest 如何支持 Express 与 Fastify,了解 AbstractHttpAdapter 如何抽象底层 HTTP 服务器。