Skip to content

反射元数据(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 的底层机制包括:

  1. 存储结构:使用 WeakMap 和嵌套 Map 存储元数据
  2. 键值管理:通过命名空间避免键冲突
  3. 装饰器实现:通过 Reflect.defineMetadata 定义元数据
  4. 读取机制:支持类级别和方法级别的元数据读取
  5. 继承支持:可以从原型链继承元数据
  6. 工具类封装:通过 Reflector 提供便捷的元数据操作

元数据的核心优势:

  1. 编译时定义,运行时使用:在编写代码时定义信息,在运行时使用
  2. 非侵入性:不影响原有代码逻辑
  3. 灵活扩展:可以定义任意的元数据信息
  4. 框架集成:与装饰器系统完美集成

通过深入理解 Reflect Metadata 机制,我们可以:

  1. 更好地使用 NestJS 的各种装饰器
  2. 创建自定义的元数据装饰器
  3. 实现复杂的运行时逻辑
  4. 构建更加灵活和可扩展的应用

在下一篇文章中,我们将探讨平台无关性:Nest 如何支持 Express 与 Fastify,了解 AbstractHttpAdapter 如何抽象底层 HTTP 服务器。