NestJS类装饰器如何正确包装类方法并保留原有装饰器功能?
正确实现类装饰器CustDec:给类方法加日志还不丢原有装饰器功能
先说说你原来的代码为啥出问题:
- 乱改原类原型+调用方式错误:你在子类构造函数里直接修改
Target.prototype,这会把所有原类实例的方法都改掉,不是只改被装饰的子类。而且你调用Target.alphaMethod()是找类本身的方法,但alphaMethod是属于实例的原型方法,自然会报“不是函数”的错。 - 原有装饰器失效:像
@Post、@Firewall这类装饰器,都是靠元数据(Metadata)给框架标记配置的。你返回的子类没继承原类的元数据,框架读不到这些配置,自然就失效了。
给你写个正确的实现,兼顾日志功能和原有装饰器:
// 如果是NestJS环境,需要引入元数据相关常量,非NestJS环境可删除此行 import { MetadataKeys } from '@nestjs/common/constants'; export function CustDec<T extends new (...args: any[]) => any>(Target: T) { // 定义继承原类的子类 const DecoratedClass = class extends Target { constructor(...args: any[]) { super(...args); console.log('@--------------------@'); } }; // 复制原类的静态属性到子类,避免丢失静态方法或装饰器配置 Object.assign(DecoratedClass, Target); // 提取原类原型上的所有方法,排除构造函数 const methodNames = Object.getOwnPropertyNames(Target.prototype).filter(name => name !== 'constructor'); methodNames.forEach(methodName => { const originalMethod = Target.prototype[methodName]; // 包装原方法,添加前后日志逻辑 DecoratedClass.prototype[methodName] = async function(...args: any[]) { console.log(`@-before ${methodName}-@`); // 执行原方法,兼容同步、异步两种场景 const result = originalMethod.apply(this, args); if (result instanceof Promise) { await result; } console.log(`@-after ${methodName}-@`); return result; }; // 复制原方法上的元数据,保证@Post这类装饰器正常生效 const metadataKeys = Reflect.getMetadataKeys(originalMethod); metadataKeys.forEach(key => { const metadata = Reflect.getMetadata(key, originalMethod); Reflect.defineMetadata(key, metadata, DecoratedClass.prototype[methodName]); }); }); // 复制原类本身的元数据,比如控制器的路由配置 const classMetadataKeys = Reflect.getMetadataKeys(Target); classMetadataKeys.forEach(key => { const metadata = Reflect.getMetadata(key, Target); Reflect.defineMetadata(key, metadata, DecoratedClass); }); return DecoratedClass; }
关键细节说明:
- 不污染原类原型:直接在子类原型上包装方法,不会影响原类的其他实例,避免全局污染。
- 元数据全复制:用
ReflectAPI把原类和原方法上的所有元数据都复制到子类,保证原有装饰器的配置能被框架识别。 - 兼容同步异步:判断原方法返回值是否为Promise,确保异步方法执行完成后再打印after日志。
- this指向正确:用
apply(this, args)让原方法执行时的this指向当前子类实例,和原来的行为完全一致。
使用示例:
@CustDec @Controller('feedback') export class FeedbackController { constructor() { // 现在调用实例方法不会报错了 this.alphaMethod(); } @Post() @Firewall() async alphaMethod() { console.log('执行alphaMethod的业务逻辑'); } }
内容的提问来源于stack exchange,提问作者Ruslan Plastun
相关产品推荐
相关产品推荐

