装饰器原理

30 阅读3分钟

NestJS 源码解析:装饰器原理

深入 TypeScript 装饰器和 reflect-metadata,揭秘 NestJS 的元数据驱动设计。

装饰器基础

TypeScript 装饰器是一种特殊的声明,可以附加到类、方法、属性或参数上。

类装饰器

function Controller(prefix: string): ClassDecorator {
  return (target: Function) => {
    Reflect.defineMetadata('path', prefix, target);
  };
}

@Controller('cats')
class CatsController {}

方法装饰器

function Get(path: string): MethodDecorator {
  return (target, key, descriptor) => {
    Reflect.defineMetadata('path', path, descriptor.value);
    Reflect.defineMetadata('method', 'GET', descriptor.value);
  };
}

class CatsController {
  @Get('all')
  findAll() {}
}

参数装饰器

function Body(): ParameterDecorator {
  return (target, key, index) => {
    const existingParams = Reflect.getMetadata('params', target, key) || [];
    existingParams.push({ index, type: 'body' });
    Reflect.defineMetadata('params', existingParams, target, key);
  };
}

class CatsController {
  create(@Body() dto: CreateCatDto) {}
}

NestJS 核心装饰器

@Injectable

// packages/common/decorators/core/injectable.decorator.ts
export function Injectable(options?: InjectableOptions): ClassDecorator {
  return (target: object) => {
    // 标记为可注入
    Reflect.defineMetadata(INJECTABLE_WATERMARK, true, target);
    // 存储作用域选项
    Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, options, target);
  };
}

@Controller

// packages/common/decorators/core/controller.decorator.ts
export function Controller(prefixOrOptions?: string | ControllerOptions): ClassDecorator {
  const defaultPath = '/';

  const [path, host, scopeOptions, versionOptions] = isUndefined(prefixOrOptions)
    ? [defaultPath, undefined, undefined, undefined]
    : isString(prefixOrOptions)
      ? [prefixOrOptions, undefined, undefined, undefined]
      : [
          prefixOrOptions.path || defaultPath,
          prefixOrOptions.host,
          { scope: prefixOrOptions.scope, durable: prefixOrOptions.durable },
          { version: prefixOrOptions.version },
        ];

  return (target: object) => {
    // 标记为控制器
    Reflect.defineMetadata(CONTROLLER_WATERMARK, true, target);
    // 存储路径
    Reflect.defineMetadata(PATH_METADATA, path, target);
    // 存储主机
    Reflect.defineMetadata(HOST_METADATA, host, target);
    // 存储作用域
    Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, scopeOptions, target);
    // 存储版本
    Reflect.defineMetadata(VERSION_METADATA, versionOptions?.version, target);
  };
}

@Get/@Post 等

// packages/common/decorators/http/request-mapping.decorator.ts
const createMappingDecorator = (method: RequestMethod) => (path?: string): MethodDecorator => {
  return (target, key, descriptor) => {
    Reflect.defineMetadata(PATH_METADATA, path || '/', descriptor.value);
    Reflect.defineMetadata(METHOD_METADATA, method, descriptor.value);
    return descriptor;
  };
};

export const Get = createMappingDecorator(RequestMethod.GET);
export const Post = createMappingDecorator(RequestMethod.POST);
export const Put = createMappingDecorator(RequestMethod.PUT);
export const Delete = createMappingDecorator(RequestMethod.DELETE);
export const Patch = createMappingDecorator(RequestMethod.PATCH);

@Inject

// packages/common/decorators/core/inject.decorator.ts
export function Inject<T = any>(token?: T): PropertyDecorator & ParameterDecorator {
  return (target: object, key: string | symbol | undefined, index?: number) => {
    const type = token || Reflect.getMetadata('design:type', target, key!);

    if (!isUndefined(index)) {
      // 参数注入
      let dependencies = Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, target) || [];
      dependencies = [...dependencies, { index, param: type }];
      Reflect.defineMetadata(SELF_DECLARED_DEPS_METADATA, dependencies, target);
    } else {
      // 属性注入
      let properties = Reflect.getMetadata(PROPERTY_DEPS_METADATA, target.constructor) || [];
      properties = [...properties, { key, type }];
      Reflect.defineMetadata(PROPERTY_DEPS_METADATA, properties, target.constructor);
    }
  };
}

参数装饰器

@Body/@Query/@Param

// packages/common/decorators/http/route-params.decorator.ts
function createRouteParamDecorator(paramtype: RouteParamtypes) {
  return (data?: any): ParameterDecorator =>
    (target, key, index) => {
      const args = Reflect.getMetadata(ROUTE_ARGS_METADATA, target.constructor, key) || {};

      Reflect.defineMetadata(
        ROUTE_ARGS_METADATA,
        {
          ...args,
          [`${paramtype}:${index}`]: {
            index,
            data,
            pipes: [],
          },
        },
        target.constructor,
        key,
      );
    };
}

export const Body = createRouteParamDecorator(RouteParamtypes.BODY);
export const Query = createRouteParamDecorator(RouteParamtypes.QUERY);
export const Param = createRouteParamDecorator(RouteParamtypes.PARAM);
export const Headers = createRouteParamDecorator(RouteParamtypes.HEADERS);
export const Req = createRouteParamDecorator(RouteParamtypes.REQUEST);
export const Res = createRouteParamDecorator(RouteParamtypes.RESPONSE);

增强器装饰器

@UseGuards

// packages/common/decorators/core/use-guards.decorator.ts
export function UseGuards(...guards: (CanActivate | Function)[]): MethodDecorator & ClassDecorator {
  return (
    target: any,
    key?: string | symbol,
    descriptor?: TypedPropertyDescriptor<any>,
  ) => {
    const isMethodDecorator = !!descriptor;

    if (isMethodDecorator) {
      // 方法级别
      const existingGuards = Reflect.getMetadata(GUARDS_METADATA, descriptor.value) || [];
      Reflect.defineMetadata(GUARDS_METADATA, [...guards, ...existingGuards], descriptor.value);
    } else {
      // 类级别
      const existingGuards = Reflect.getMetadata(GUARDS_METADATA, target) || [];
      Reflect.defineMetadata(GUARDS_METADATA, [...guards, ...existingGuards], target);
    }

    return descriptor;
  };
}

@UsePipes/@UseInterceptors/@UseFilters

实现类似,都是将增强器存储到元数据中。

元数据读取

MetadataScanner

扫描类的所有方法:

// packages/core/metadata-scanner.ts
export class MetadataScanner {
  public scanFromPrototype<T, R = any>(
    instance: T,
    prototype: object,
    callback: (name: string) => R,
  ): R[] {
    const methodNames = this.getAllMethodNames(prototype);
    return methodNames.map(callback);
  }

  public getAllMethodNames(prototype: object): string[] {
    const isMethod = (prop: string) => {
      const descriptor = Object.getOwnPropertyDescriptor(prototype, prop);
      return !descriptor?.get && !descriptor?.set && isFunction(prototype[prop]);
    };

    return Object.getOwnPropertyNames(prototype).filter(
      prop => prop !== 'constructor' && isMethod(prop),
    );
  }
}

PathsExplorer

探索控制器的路由:

// packages/core/router/paths-explorer.ts
export class PathsExplorer {
  public scanForPaths(instance: Controller): RouteDefinition[] {
    const prototype = Object.getPrototypeOf(instance);

    return this.metadataScanner.scanFromPrototype(instance, prototype, method => {
      const path = Reflect.getMetadata(PATH_METADATA, prototype[method]);
      const requestMethod = Reflect.getMetadata(METHOD_METADATA, prototype[method]);

      return {
        path: this.validatePath(path),
        requestMethod,
        targetCallback: prototype[method],
        methodName: method,
      };
    });
  }
}

自定义装饰器

组合装饰器

import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common';

export function Auth(...roles: string[]) {
  return applyDecorators(
    SetMetadata('roles', roles),
    UseGuards(AuthGuard, RolesGuard),
  );
}

// 使用
@Auth('admin')
@Get()
findAll() {}

applyDecorators 实现

// packages/common/decorators/core/apply-decorators.ts
export function applyDecorators(
  ...decorators: Array<ClassDecorator | MethodDecorator | PropertyDecorator>
) {
  return <TFunction extends Function, Y>(
    target: TFunction | object,
    propertyKey?: string | symbol,
    descriptor?: TypedPropertyDescriptor<Y>,
  ) => {
    for (const decorator of decorators) {
      if (target instanceof Function && !descriptor) {
        (decorator as ClassDecorator)(target);
        continue;
      }
      (decorator as MethodDecorator | PropertyDecorator)(
        target,
        propertyKey!,
        descriptor!,
      );
    }
  };
}

自定义参数装饰器

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;
    return data ? user?.[data] : user;
  },
);

// 使用
@Get()
findOne(@User('id') userId: string) {}

createParamDecorator 实现

// packages/common/decorators/http/create-route-param-metadata.decorator.ts
export function createParamDecorator<T = any, I = any, O = any>(
  factory: CustomParamFactory<T, I, O>,
): (...dataOrPipes: (Type<PipeTransform> | PipeTransform | T)[]) => ParameterDecorator {
  return (...dataOrPipes: (Type<PipeTransform> | PipeTransform | T)[]): ParameterDecorator => {
    const [data, ...pipes] = dataOrPipes;

    return (target, key, index) => {
      const args = Reflect.getMetadata(ROUTE_ARGS_METADATA, target.constructor, key) || {};

      const paramtype = uid(21);
      Reflect.defineMetadata(
        ROUTE_ARGS_METADATA,
        {
          ...args,
          [`${paramtype}:${index}`]: {
            index,
            factory,
            data,
            pipes,
          },
        },
        target.constructor,
        key,
      );
    };
  };
}

元数据常量

// packages/common/constants.ts
export const MODULE_METADATA = {
  IMPORTS: 'imports',
  PROVIDERS: 'providers',
  CONTROLLERS: 'controllers',
  EXPORTS: 'exports',
};

export const PATH_METADATA = 'path';
export const METHOD_METADATA = 'method';
export const ROUTE_ARGS_METADATA = '__routeArguments__';
export const GUARDS_METADATA = '__guards__';
export const INTERCEPTORS_METADATA = '__interceptors__';
export const PIPES_METADATA = '__pipes__';
export const EXCEPTION_FILTERS_METADATA = '__exceptionFilters__';
export const INJECTABLE_WATERMARK = '__injectable__';
export const CONTROLLER_WATERMARK = '__controller__';

总结

NestJS 装饰器系统的核心:

  1. reflect-metadata:存储和读取元数据
  2. 类装饰器:标记类的角色(Controller、Injectable)
  3. 方法装饰器:定义路由和增强器
  4. 参数装饰器:定义参数来源
  5. 组合装饰器:applyDecorators 组合多个装饰器
  6. 自定义装饰器:createParamDecorator 创建参数装饰器

下一篇我们将分析路由系统的实现。


📦 源码位置:packages/common/decorators/

下一篇:NestJS 路由系统