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,当然还包括之前测试的PersonModule,OrderModule等等
@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 指定注入的 provider 的 token 即可。
在构造器参数里指定 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) {}}用字符串或者 class 做 token 的 provider,都可以正确被注入到目标对象。用 class 做 token 可以省去@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函数有两个参数了,其实意思很明显,需要将具体的值传入到方法中去,但是值从哪里来?不错,可以直接通过容器给我们注入进来,因为car和app_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'这两个我们在之前都已经注册到了容器中