【NestJS】深入理解NestJS装饰器原理
NestJS 的装饰器系统是整个框架的“灵魂之一”,理解它的原理、运行时机和执行顺序,就能真正搞懂控制器、依赖注入、管道、拦截器等机制的底层逻辑。
🌱 一、装饰器是什么?(TypeScript 层面)
装饰器其实就是一个能改造类或类成员的函数。
NestJS 本质上只是利用了 TypeScript 提供的装饰器语法糖 + Reflect Metadata 机制。
简单来说:
- TypeScript 在编译时遇到
@Something时,会调用这个装饰器函数; - 装饰器函数可以拿到目标(类、方法、属性、参数);
- NestJS 再利用这些信息,在启动时构建路由、依赖注入等逻辑。
🧩 二、装饰器的类型(Nest 常用的 4 种)
| 类型 | 示例 | 应用于 | 常见用途 |
|---|---|---|---|
| 类装饰器 | @Controller()、@Injectable() | 类 | 定义控制器、模块、服务等 |
| 方法装饰器 | @Get()、@Post() | 类的方法 | 定义路由和请求方法 |
| 参数装饰器 | @Body()、@Param() | 方法参数 | 提取请求参数 |
| 属性装饰器 | @Inject()、@Autowired() | 类的属性 | 实现依赖注入 |
🧠 三、装饰器的运行时机(非常关键)
NestJS 的装饰器 不是运行时调用的,而是在类定义阶段就执行!
也就是说:
@Controller('user')
export class UserController {@Get()getUser() {}
}
当 Nest 应用还没启动时,这两个装饰器已经执行了:
- TypeScript 在加载
UserController类时; - 它会调用
@Controller(); - 然后再调用
@Get(); - 最后把元数据存到
Reflect(反射)里。
Nest 启动时会遍历这些类 → 读取元数据 → 生成路由表。
👉 所以装饰器运行在“类加载阶段”,而不是请求到来时。
🔍 四、简单示例:自定义装饰器运行原理
import 'reflect-metadata';function MyController(prefix: string) {return (target: any) => {Reflect.defineMetadata('prefix', prefix, target);console.log(`[装饰器执行] 定义控制器:${prefix}`);};
}function MyGet(path: string) {return (target: any, key: string, descriptor: PropertyDescriptor) => {Reflect.defineMetadata('path', path, descriptor.value);console.log(`[装饰器执行] 定义路由:${path}`);};
}@MyController('user')
class UserController {@MyGet('list')getList() {}
}
运行结果(还没创建任何实例):
[装饰器执行] 定义路由:list
[装饰器执行] 定义控制器:user
✅ 装饰器在类被定义时立即执行,而不是在调用 new UserController() 或请求时。
⚙️ 五、多个装饰器的执行顺序
这是最容易搞混的地方。我们来分两种情况:
① 同一目标多个装饰器
比如:
@A()
@B()
class Test {}
执行顺序:
- 从下到上执行(B → A)
② 嵌套装饰器(函数调用嵌套)
比如:
@A(B())
class Test {}
执行顺序:
- 从内到外执行函数调用(先调用
B()再执行A()); - 装饰应用顺序仍然是从下到上。
🧪 举个 Nest 风格例子:
function Log(name: string) {return (target: any, key: string, descriptor: PropertyDescriptor) => {console.log(`运行 Log(${name})`);};
}function Auth(role: string) {return (target: any, key: string, descriptor: PropertyDescriptor) => {console.log(`运行 Auth(${role})`);};
}class UserController {@Log('list')@Auth('admin')getList() {}
}
输出结果:
运行 Auth(admin)
运行 Log(list)
✅ 顺序:从下往上执行
也就是 @Auth 比 @Log 先执行。
🏗️ 六、NestJS 真实装饰器执行流程图
Controller 装饰器 (@Controller)│├──> 注册控制器元数据│└──> 遍历控制器方法│├──> 执行路由装饰器 (@Get, @Post...)│ ├──> 记录 HTTP 方法与路径│ └──> 绑定到控制器的 method 上│└──> 执行参数装饰器 (@Body, @Param...)├──> 记录参数类型与来源└──> 创建请求解析逻辑
最终 Nest 启动时会读取这些 Reflect Metadata,构建:
- 路由映射表
- 参数解析逻辑
- 依赖注入容器
- 生命周期绑定
🚀 七、总结一句话(记忆口诀)
“装饰器定义时执行,Reflect 记下元数据,Nest 启动时读取,最终请求时应用。”
| 阶段 | 发生了什么 |
|---|---|
| 编译加载时 | 装饰器执行,元数据写入 Reflect |
| Nest 启动时 | Nest 读取元数据,注册控制器和依赖 |
| 请求到来时 | 使用元数据完成参数注入、验证、执行等逻辑 |
🎯 八、拓展:查看装饰器生成的元数据
你可以在 Nest 启动后打印:
const prefix = Reflect.getMetadata('path', UserController);
console.log(prefix);
或者调试时看:
console.log(Reflect.getMetadataKeys(UserController));
就能看到 Nest 的内部“路由表是怎么生成的”。
