, ,

IoC在Nest中的应用

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

在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

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

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';
}
}

属性注入

上面的代码,无论是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'这两个我们在之前都已经注册到了容器中

Previous Post

Next Post

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

About the Author

每个人都有自己得时区,在自己得时区里,一切都是准时的。

BlockSpare — News, Magazine and Blog Addons for (Gutenberg) Block Editor