一、IOC控制反转和依赖注入
控制反转
传统的编程模式中,对象的创建和依赖是由使用对象的代码来控制,需要知道什么时候创建/使用对象,而在IOC中,控制权交给了容器,负责创建对象并且解决对象之间的依赖关系,使得在修改某一类的时候,调用者不需要重新修改其他创建对象的地方,实现了对象之间的松耦合。
依赖注入
控制反转是一种思想,它告诉我们解决问题的思路,但没有具体告诉我们应该怎么做。而依赖注入,就是实现IOC的方式,通过依赖注入(DI),容器将依赖关系自动注入到指定对象中,而不是让对象自己去创建或者查找其他依赖的对象。实现方式可以是通过构造函数注入、属性注入或者方法注入。
举例:
Vue中也体现了这一点:Inject和Provider,在组件通信中,祖先组件中提供依赖给后代组件使用,后代无需关心祖先组件是如何创建和销毁依赖。
容器
IOC中的容器可以理解为依赖调度中心,负责管理所有的对象生命周期管理和依赖关系,在Nest中有内置的IOC容器,它是IOC的核心。通过配置中心或注解的方式来定义对象之间的依赖关系。
示例
创建一个公共类 A, 业务类 B 和 业务类 C 分别依赖 A
// 公共类
class A {
name: string
constructor() {
this.name = "jmin"
}
}
// 业务类
class B {
b: string
constructor() {
this.b = new A().name
}
}
// 业务类
class C {
c: string
constructor() {
this.c = new A().name
}
}
当存在这种依赖关系的时候,尝试修改一下 公共类A,将name改为动态赋值。
class A {
name: string
constructor(name: string) {
this.name = name
}
}
那么,这时候依赖公共类的业务类B和C都需要在对象创建的时候修改创建方式,在大型应用中,如果有100个业务类依赖公共类A呢?
而IOC/DI为了解决这个问题应运而生。
我们来看下通过IOC方式维护的示例:
// 公共类
class A {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 业务类
class B {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 容器类
class Container {
map: any;
constructor() {
this.map = {};
}
provide(key: string, value: any) {
this.map[key] = value;
}
get(key: string) {
return this.map[key];
}
}
// 新建容器进行收集引用
const container = new Container();
// 提供依赖
container.provide('a', new A('a'));
container.provide('b', new B('b'));
// C类可以随意在容器中获取想要依赖的类
class C {
a: A;
b: B;
constructor() {
this.a = container.get('a');
this.b = container.get('b');
}
}
上述代码中,容器中维护着多个公共类A、B,它们在某个时机统一注册到容器中(依赖注入),而此时业务类C可以随意在容器中获取想要依赖的类,并且当A、B改变,不需要修改到调用方,实现逻辑解耦。
Nest中实现依赖注入
Nest注册依赖
在Nest中,IOC容器是内置的nest运行时系统,使用module向容器注册依赖
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
module可以接受多个诸如controllers、providers的依赖。
定义提供者provider
通过@Injectable()装饰器将service类标识为提供者,此时,IOC容器将会接管这个类的控制权。
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
将提供者注入到控制器中
Controller不做具体业务逻辑,它只负责分发请求,具体的请求逻辑由Service提供者管理,所以我们将提供者注入到控制器中,通过构造函数注入声明了对AService的依赖。
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
总体的注册过程如下: 当 Nest IoC 容器实例化一个 AppController 时,它首先查找任何依赖。 当它找到AppService 依赖时,它会根据注册步骤对 AppService 令牌执行查找,返回AppService 类。
二、装饰器
装饰器是一种特殊的类型声明,他可以附加在类,方法,属性,参数上面
注意:
装饰器是实验特性,需要在 tsconfig.json 中启用:
{
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
类装饰器
给类添加元数据、注册依赖,主要是通过@符号添加装饰器
他会自动把class的构造函数传入到装饰器的第一个参数 target
然后通过prototype可以自定义添加属性和方法
const decotators = (target: any) => {
target.prototype.name = 'aiolimp';
};
@decotators
class Aiolimp {
constructor() {}
}
const a: any = new Aiolimp();
console.log(a.name);
属性装饰器
设置默认值、自动绑定、依赖注入等。同样使用@符号给属性添加装饰器,他会返回两个参数
-
原型对象
-
属性的名称
const currency: PropertyDecorator = (target: any, key: string | symbol) => {
console.log(target, key); // {} name
};
class A {
@currency
public name: string;
constructor() {
this.name = '';
}
getName() {
return this.name;
}
}
参数装饰器
获取请求参数、上下文信息等,同样使用@符号给属性添加装饰器,他会返回两个参数
- 原型对象
- 方法的名称
- 参数的位置,从0开始
const cur: ParameterDecorator = (target: any, key: string | symbol | undefined, index: number) => {
console.log(target, key, index); // {} getName 1
};
class Xiaoman {
public name: string;
constructor() {
this.name = '';
}
getName(name: string, @cur age: number) {
return this.name;
}
}
方法装饰器
用于权限校验、日志打印、缓存处理等,同样使用@符号给属性添加装饰器,他会返回两个参数
-
原型对象
-
方法的名称
-
属性描述符 可写对应writable,可枚举对应enumerable,可配置对应configurable
const fun: MethodDecorator = (target: any, key: string | symbol, descriptor: any) => {
console.log(target, key, descriptor);
// {} getName {
// value: [Function: getName],
// writable: true,
// enumerable: false,
// configurable: true
// }
};
class B {
public name: string;
constructor() {
this.name = '';
}
@fun
getName(name: string, age: number) {
return this.name;
}
}
| 类型 | 作用 | 示例框架 |
|---|---|---|
| 类装饰器 | 给类添加元数据、注册依赖 | @Injectable()(NestJS) |
| 方法装饰器 | 权限校验、日志打印、缓存处理等 | @Get()(NestJS) |
| 属性装饰器 | 设置默认值、自动绑定、依赖注入等 | @Inject() |
| 参数装饰器 | 获取请求参数、上下文信息等 | @Body()、@Param() |
示例:通过装饰器实现一个Get请求
安装axios依赖
npm install axios -S
定义控制器UserController
class UserController {
constructor() {}
}
定义装饰器
带参数的装饰器
ES6中不支持直接调用装饰器,当需要支持传参时(比如 @Get('/user') 中的 '/user'),要 return 一个函数,通过装饰器工厂,也叫柯里化。
const Get = (url: string): MethodDecorator => {
return (target: any, key: string | symbol, descriptor: any) => {
};
};
class UserController {
constructor() {}
@Get('https://api.apiopen.top/api/getHaoKanVideo?page=0&size=10')
getUser(res: any, status: any) {
console.log(res.data.result.list, status);
}
}
- 外层函数 Get(path: string) 是你传参的部分;
- 内层函数 (target, key,descriptor) 是装饰器真正作用于类方法时调用的;
不带参数的装饰器
如果你写一个 不需要参数的装饰器,可以省略外层 return:
function Log(target: any, propertyKey: string) {
console.log(`${propertyKey} 被装饰`)
}
@Log
hello() {}
Get请求完整代码
import axios from 'axios';
// 定义装饰器
const Get = (url: string): MethodDecorator => {
return (target: any, key: string | symbol, descriptor: any) => {
// 定义 descriptor 的类型 通过 descriptor描述符里面的value 把axios的结果返回给当前使用装饰器的函数
const func = descriptor.value; // 获取原始方法getUser
axios
.get(url)
.then((res) => {
func(res, {
status: 200,
});
})
.catch((err) => {
func(err, {
status: 200,
});
});
return descriptor;
};
};
// 定义控制器
// - 当类被加载时,装饰器会立即执行
// - Get 装饰器在返回的装饰器函数中直接调用了axios.get()
// - 请求完成后,又直接调用了原始方法 func(res, {status: 200})
class UserController {
constructor() {}
@Get('https://api.apiopen.top/api/getHaoKanVideo?page=0&size=10')
getUser(res: any, status: any) {
console.log(res.data.result.list, status);
}
}
Nest中的装饰器
在 NestJS 中,装饰器是核心机制之一,广泛用于控制反转(IoC)、依赖注入(DI)、路由、参数提取、元数据定义等场景。Nest 的装饰器本质上是函数,通过它们可以声明式地定义类的行为、结构和元数据。
1.类装饰器(用于定义组件/模块/控制器等)
| 装饰器 | 说明 |
|---|---|
| @Controller() | 声明该类为控制器(Controller) |
| @Injectable() | 声明该类可以被注入(服务等) |
| @Module() | 声明该类为模块 |
| @Global() | 声明该模块为全局模块 |
@Injectable()
export class UserService {
getUser() {
return { name: 'Tom' }
}
}
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Get()
getUser() {
return this.userService.getUser()
}
}
2.方法装饰器(用于声明路由、生命周期钩子)
| 装饰器 | 说明 |
|---|---|
| @Get() | 声明 GET 路由 |
| @Post() | 声明 POST 路由 |
| @Put() | 声明 PUT 路由 |
| @Delete() | 声明 DELETE 路由 |
| @OnModuleInit() | 生命周期钩子方法装饰器 |
3.参数装饰器(用于提取请求参数)
| 装饰器 | 说明 |
|---|---|
| @Param() | 获取 URL 参数 |
| @Query() | 获取查询参数(?key=value) |
| @Body() | 获取 POST/PUT 请求体 |
| @Req() | 获取原始请求对象(Request) |
| @Res() | 获取原始响应对象(Response) |
| @Headers() | 获取请求头信息 |
@Get(':id')
getUser(@Param('id') id: string, @Query('verbose') verbose: boolean) {
return { id, verbose }
}
4.自定义装饰器(基于 Reflect元数据)
可以使用 Reflect.defineMetadata() 与 Reflect.getMetadata() 自定义装饰器行为:
import 'reflect-metadata'
function Role(role: string) {
return Reflect.metadata('role', role)
}
@Role('admin')
class AdminService {}
5.依赖注入相关装饰器(高级)
| 装饰器 | 说明 |
|---|---|
| @Inject() | 显式注入标识符或Token |
| @Optional() | 注入失败时不抛错 |
| @HostParam() | 用于网关参数注入(WebSocket等) |
NestJS 的装饰器实现了声明式编程风格,将业务逻辑与框架结构清晰分离,大幅提升可维护性和开发效率。它们是 依赖注入(DI)机制、模块化架构 和 路由控制 的基础。