Skip to content

IoC

IoC在Nest中的应用#

在xxx.controller.ts文件中,我们都会有如下的装饰器,比如app.controller.ts

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
// ......
}

AppController类通过**@Controller**装饰器来装饰,表示它可以进行依赖注入,由Nest内部的IoC容器接管。

接着比如app.service.ts

@Injectable()
export class AppService {
//......
}

AppService类通过**@Injectable进行装饰,表示这个类可以被注入,注意@Injectable不单是可以装饰service(服务)类,包括过滤器(Filter)、拦截器(Intercepter)、提供者(Provide)、网关(Gateway)等等,在没有特殊情况下,Nest中所需要依赖注入的模块都可以使用@Injectable**进行标记,以便在IoC容器中进行收集和管理。

为什么控制器是单独使用@Controller来装饰?

控制器(Controller)只用于处理请求,不作为依赖对象被其他对象组件注入,我们其实可以把控制器看做是消费者,而服务(Service)中间件(Middleware)是提供者

这些组件会在AppModule中进行引入

@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

@Module装饰器在Nest中用于定义模块,这些模块包含需要注入的组件。

控制器仅仅只能作为消费者被注入,而提供者(Providers),比如服务(Service),既能作为依赖被注入,也可以注入到其他依赖对象中。

除此之外,在Nest中实现模块化管理非常简单。imports属性用于引入其他模块,这有助于实现功能逻辑的分组和重用。比如我们之前的UserModule,在我们使用了nest g resource user命令之后,在AppModule中就自动引入了UserModule,当然还包括之前测试的PersonModuleOrderModule等等

@Module({
imports: [UserModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

比如我们这里直接引入了UserModule,那么在User模块中,我们是这样的:

@Module({
imports: [],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}

UserController类中:

@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
findAll(): string {
return this.userService.findAll()
}
}

UserService类中:

@Injectable()
export class UserService {
findAll(): string {
return 'This action returns all users'
}
}

这样,启动服务之后,就可以通过/user路径访问到findAll接口内容了

属性注入#

上面的代码,无论是AppController,还是UserController中,都是是通过构造器注入UserService,除此之外,还能通过属性注入

@Controller('user')
export class UserController {
@Inject(UserService)
private userService: UserService;
// constructor(private readonly userService: UserService) {}
@Get()
findAll(): string {
console.log('user controller');
return this.userService.findAll();
}
}

provider 注入#

前面我们就简单提了一下provider是作为提供者,其实提供的就是可以注入的内容,比如我们前面是这样写的:

@Module({
imports: [UserModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

意思就是AppService可以被注入,这里其实是一种简写,完整的写法应该是下面这个样子:

@Module({
imports: [UserModule],
controllers: [AppController],
providers: [
{
provide: AppService,
useClass: AppService,
},
],
})
export class AppModule {}

通过 provide 指定 token,通过 useClass 指定对象的类,Nest 会自动对它做实例化后用来注入。

AppController 的构造器里参数里声明了 AppService 的依赖,就会自动注入

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
// ......
}

如果不想用构造器注入,也可以属性注入

@Controller('admin')
export class AppController {
@Inject(AppService)
private appService: AppService
// ......
}

通过 @Inject 指定注入的 providertoken 即可。

在构造器参数里指定 AppService 的依赖的时候不是没有指定 token吗?那是因为我们直接把AppService 这个class本身作为了token

这个token只是一个标识,因此,也可以是一个字符串,比如:

@Module({
imports: [UserModule],
controllers: [AppController],
providers: [
{
provide: 'app_service',
useClass: AppService,
},
],
})
export class AppModule {}

那么,无论是构造器参数注入,还是属性注入,我们就必须引入这个字符串的token

@Controller()
export class AppController {
@Inject('app_service')
private appService: AppService
}

或者:

@Controller()
export class AppController {
constructor(@Inject('app_service') private readonly appService: AppService) {}
}

字符串或者 classtokenprovider,都可以正确被注入到目标对象。用 classtoken 可以省去@Inject,比较简便而已。

直接注入值#

既然provider可以直接指定一个class,那其实也能指定一个,比如

@Module({
imports: [UserModule],
controllers: [AppController],
providers: [
{
provide: 'app_service',
useClass: AppService,
},
{
provide: 'car',
useValue: {
brand: 'BYD',
price: 100000,
},
},
],
})
export class AppModule {}

那么,在controller中,我们就可以直接注入这个**’car‘**

@Controller()
export class AppController {
@Inject('app_service')
private appService: AppService
@Inject('car')
private car: { brand: string; price: number }
@Get('car')
hello(): string {
return `Hello, I have a ${this.car.brand} car, it's price is ${this.car.price}`
}
}

动态注入值#

provider 的值可能是动态产生的,Nest 也同样支持,通过工厂的方式,其实也就是函数的方式注入

const createRandomFactory = () => Math.random()
@Module({
imports: [UserModule],
controllers: [AppController],
providers: [
{
provide: 'app_service',
useClass: AppService,
},
{
provide: 'car',
useValue: {
brand: 'BYD',
price: 100000,
},
},
{
provide: 'random',
useFactory: createRandomFactory,
},
],
})
export class AppModule {}

这样,在app.controller.ts中,也可以注入这个random

@Controller()
export class AppController {
// ......
@Inject('random')
private random: number
@Get('random')
randomNum(): number {
return this.random
}
}

而且,useFactory既然可以是函数,那么当然可以给他传递参数,而且注意一个问题,我们所有的对象都是通过Nest容器帮我们管理的,所以,我们甚至可以写成下面这个样子:

const createRandomFactory = (
car: { brand: string; price: number },
appService: AppService
) => {
return {
random: Math.random(),
brand: car.brand,
hello: appService.getHello(),
}
}

现在这个createRandomFactory函数有两个参数了,其实意思很明显,需要将具体的值传入到方法中去,但是值从哪里来?不错,可以直接通过容器给我们注入进来,因为carapp_service这两个之前我们都是有注册的。

所以,接下来的写法:

const createRandomFactory = (
car: { brand: string; price: number },
appService: AppService
) => {
return {
random: Math.random(),
brand: car.brand,
hello: appService.getHello(),
}
}
@Module({
imports: [UserModule],
controllers: [AppController],
providers: [
{
provide: 'app_service',
useClass: AppService,
},
{
provide: 'car',
useValue: {
brand: 'BYD',
price: 100000,
},
},
{
provide: 'random',
useFactory: createRandomFactory,
inject: ['car', 'app_service'],
},
],
})
export class AppModule {}

通过 inject注入了两个token'car''app_service'这两个我们在之前都已经注册到了容器中