nestjs-属性注入和构造函数注入的区别
在 NestJS 中,依赖注入的工作机制如下:
- 构造函数注入:
NestJS 在实例化类时,会首先解析构造函数中的依赖,并注入相应的实例。
因此,构造函数中的依赖在类实例化时就已经准备好了。
- 属性注入:
属性注入是通过 @Inject() 装饰器实现的。
NestJS 会在类实例化之后,再为属性赋值。
因此,在构造函数中访问属性注入的依赖时,属性还未被赋值,所以是 undefined
。
只有在构造函数执行完成后,属性才会被正确注入。
在你的场景中:
你在 controller 中通过属性注入 Reflector。
在构造函数中打印 this.reflector 时,Reflector 还未被注入,所以是 undefined。
在后续的请求方法(如 get 方法)中,Reflector 已经被注入,所以可以正常访问。
代码示例
以下是一个示例代码,展示了这个问题:
复制
import { Controller, Get, Inject } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Controller('test')
export class TestController {
@Inject(Reflector)
private reflector: Reflector;
constructor() {
console.log('Constructor:', this.reflector); // undefined
}
@Get()
test() {
console.log('GET Method:', this.reflector); // Reflector 实例
return 'Hello World';
}
}
解决方案
- 使用构造函数注入(推荐)
NestJS 官方推荐使用构造函数注入,因为它能确保依赖在类实例化时就已经准备好,避免了属性注入的时机问题。
import { Controller, Get } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Controller('test')
export class TestController {
constructor(private readonly reflector: Reflector) {
console.log('Constructor:', this.reflector); // Reflector 实例
}
@Get()
test() {
console.log('GET Method:', this.reflector); // Reflector 实例
return 'Hello World';
}
}
- 延迟访问属性注入的依赖
如果你必须使用属性注入,可以在构造函数中避免直接访问属性注入的依赖,而是在后续方法中访问。
import { Controller, Get, Inject } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Controller('test')
export class TestController {
@Inject(Reflector)
private reflector: Reflector;
constructor() {
// 不要在构造函数中访问 this.reflector
}
@Get()
test() {
console.log('GET Method:', this.reflector); // Reflector 实例
return 'Hello World';
}
}
- 使用生命周期钩子
如果你需要在类实例化后立即访问属性注入的依赖,可以使用 NestJS 的生命周期钩子(如 OnModuleInit)。
import { Controller, Get, Inject, OnModuleInit } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Controller('test')
export class TestController implements OnModuleInit {
@Inject(Reflector)
private reflector: Reflector;
onModuleInit() {
console.log('onModuleInit:', this.reflector); // Reflector 实例
}
@Get()
test() {
console.log('GET Method:', this.reflector); // Reflector 实例
return 'Hello World';
}
}
总结
属性注入的依赖在构造函数中不可用,因为 NestJS 会在类实例化之后才为属性赋值。
推荐使用构造函数注入,这是 NestJS 的标准做法,能避免时机问题。
如果必须使用属性注入,可以在生命周期钩子(如 OnModuleInit)或后续方法中访问注入的依赖。
nestjs推荐使用构造函数注入,如果作为父类,那么构造函数无法注入(只能通过子类在构造函数调用 super(...)传递参数),此时可以使用属性 @Inject() 注入.
Reflect.getMetadata() 和 reflector 的使用场景
- 在使用属性注入的类的构造函数中,推荐使用 Reflect.getMetadata,因为此时属性注入的实例等到所在类实例化后才能获取,如果是通过构造函数注入,两者则没有区别
reflect-metadata的原理
reflect-metadata
的源码很简单,就是给Reflect
对象增加了几个方法,并且用WeakMap存储key为target,值为Map,该Map里存储key为对象的property,值为Map,最里层的map就是存储元数据键值对,如
- Reflect.defineMetadata(‘key’,value,target,property?) 给target变量或者其target的property属性定义键为key的元数据value
- Reflect.getMetadata(‘key’,target,property?) 获取target或target.property(可以通过原型获取)键为key的元数据
只能给对象或者对象和key或者函数(函数也是对象)定义元数据
import "reflect-metadata";
class A {
methodA() {}
}
const a = new A();
const metadatKey = "metadatKey";
// 给实例方法添加元数据
Reflect.defineMetadata(metadatKey, "a.methodA元数据", a.methodA);
console.log(Reflect.getMetadata(metadatKey, a.methodA));
function fn() {}
const b = {};
const primitiveVar = ""; // 会报错,因为target只允许是对象或者函数(函数也是对象,源码中就是这样判断的)
Reflect.defineMetadata(metadatKey, "fn元数据", fn);
Reflect.defineMetadata(metadatKey, "b元数据", b);
Reflect.defineMetadata(metadatKey, "primitiveVar元数据", primitiveVar);
console.log(Reflect.getMetadata(metadatKey, fn)); //fn元数据
console.log(Reflect.getMetadata(metadatKey, b)); //b元数据
console.log(Reflect.getMetadata(metadatKey, primitiveVar)); // 报错
如下所示