Nest中的AOP思想

98 阅读6分钟

Provider的多种注入

  • Nest实现了IOC容器,从入口开始扫描,分析Module之间的引用关系,对象之间的依赖关系,自动把provider注入到目标对象中。

  • provider的几种注入方式:

      1. 简写方式: AppService => { provide: AppService(token), useClass: AppService }, 在AppController中构造器中注入AppService对象, 就会自动注入
      1. 属性注入: 通过@Inject指定注入的provider的token即可。
      • @Inject(AppService) // provide 指定的token
        private readonly appService: AppService;
        
      1. 构造器注入: 通过构造器注入指定provider的token即可。
      •  {provide: "app_service", useClass: AppService} // 字符串, 需要使用@Inject进行指定token
         constructor(@Inject('app_service') private readonly appService: AppService) {}
        
      1. 除了指定class外, 还可以直接指定一个值没让IOC来注入
      • {provide: "app_service", useValue: {name: "aaa", age: 20}} // 直接指定一个值
        constructor(@Inject('app_service') private readonly appService: AppService) {}
        
      1. provider的值可能是动态的,Nest的支持方式

      • {provide: "person2", useFactory() => {name: "bbb", desc: "ccc"}} // 动态产生的时候使用的是useFactory函数动态生成一个对象
        
      • useFactory支持通过参数注入别的provider, 通过inject声明两个token,一个是字符串的person, 两一个是class的AppService
      
            {
            provide: 'person3',
            useFactory(person: { name: string }, appService: AppService) {
              return {
                name: person.name,
                age: appService.getHello(),
              };
            },
            inject: ['person', AppService],
          },
      
      1. useFactory支持异步async,nest会等拿到异步方法的结果之后再注入
      1. provider还可以通过useExisting来指定别名。{provide: "person4", useExisting: "person2"}

全局模块

  • 模块通过exports导出provider,另一个模块需要imports才能使用这些rpovider,但是如果这个模块被很多模块依赖了,每次导入的时候就会很麻烦,所以需要设置为全局模块 (@Global()), 这个时候就不需要在别的模进行imports带入了

生命周期

  • 在Nest启动的时候, 会递归解析Module依赖,然后扫描其中的provider, controller,注入它的依赖,全部解析后,会监听网络端口,开始处理请求。

  • nest中的生命周期

      1. 创建(OnModuleInit)和启动(OnApplicationBoostrap)声明周期: 首先,递归初始化模块,会依次调用模块的controller, provider的onModuleInit方法,然后在调用module的onModuleInit方法。全部初始化完成之后,再依次调用模块内的controller, provider的onApplicationBoostrap方法,然后在调用module的onApplicationBoostrap方法。然后开始监听网络端口。
      1. 关闭(OnModuleDestroy)和销毁(BeforeApplitionShutdown)声明周期: 先调用模块controller, provider的onModuleDestory方法,然后调用调用module的onModuleDestory方法,然后依次调用模块内的controller, provider的beforeApplicationShutdown方法,调用module的beforeApplicationShutdown方法, 最后关闭网络端口。最后调用每个模块的onApplicationShutdown方法。
    • beforeApplicationShutdown和onApplicationShutdown的区别: beforeApplicationShutdown是在关闭之前调用的,onApplicationShutdown是在关闭之后调用的。beforeApplicationShutdown是可以拿到single系统信号。这些终止信息是别的进行传递过来的,让系统做一些销毁的事情。比如k8s管理容器的时候, 可以通过这个信号来通知它。

    • 一般都是通过moduleRef取出一些provider来销毁,比如关闭连接等。moduleRef就是当前模块的引用。

      • this.moduleRef.get(AppService) 调用get方法,传入token,就可以获取到对应的provider对象。

Aop架构

  • Aop的好处是可以把一些通用逻辑分离到切面中,保持业务逻辑纯粹性,这样切换逻辑就可以复用,还可以动态的增删。

      1. 中间件Middleware
      • 全局中间件:在main中使用app.use注入的中间件, 在handler之前增加一些可复用的逻辑。

      • 路由中间件:nest g middleware log

        •     @Injectable()
              export class LogMiddleware implements NestMiddleware {
              use(req: Request, res: Response, next: NextFunction) {
                  console.log('log middleware');
                  next();
              }
              }
          
              export class PersonModule implements NestModule {
                  configure(consumer: MiddlewareConsumer) {
                      // 匹配到的路由才生效
                      consumer.apply(LogMiddleware).forRoutes('person/log*');
                  }
              }
          
      1. Guard
      • 路由守卫, 可以用在调用某个Controller之前判断权限,返回true或者false来决定是否放行。使用@UseGuards给某个路由添加权限校验
      • 全局守卫: app.useGlobalGuards(new AuthGuard()) 或者使用AppModule中提供者模式 {provide: APP_GUARD, useClass: AuthGuard}, 全局守卫是在所有路由之前执行的, 通过provide注入的方式Guard在IOC容器中,可以注入别的provider. 而Global的方式就不能访问到。
    1. Interceptor
    • 可以在目标controller方法前后加入一些逻辑。
    1. Interceptor和Middleware的区别
    • 主要是参数不同,interceptor可以调用controller和handler. controller和handler加一些metadata,这种就只有interceptor和guard才可以取出参数,middleware不能。
      1. Interceptor是支持单路由启用@UseInterceptors(TimeInterceptor)
      1. Interceptor是支持Conltroller级别启用。作用于这个controller的handler方法.
      1. Interceptor是支持全局启用app.useGlobalInterceptors(new TimeInterceptor()),作用于全部的controller。还可以像Guard一样在AppModule中指定提供者使用IOC容器注入的方式{ptovide:APP_INTERCEPTOR, useClass: TimeInterceptor}
    1. Pipe
  • 除了路由的权限控制、目标 Controller 之前之后的处理这些都是通用逻辑外,对参数的处理也是一个通用的逻辑,所以 Nest 也抽出了对应的切面.Pipe 是管道的意思,用来对参数做一些检验和转换.

  • Pipe 要实现 PipeTransform 接口,实现 transform 方法,里面可以对传入的参数值 value 做参数验证,比如格式、类型是否正确,不正确就抛出异常。也可以做转换,返回转换后的值

  • Nest 内置了一些 Pipe,从名字就能看出它们的意思:

    • ValidationPipe
    • ParseIntPipe
    • ParseBoolPipe
    • ParseArrayPipe
    • ParseUUIDPipe
    • DefaultValuePipe
    • ParseEnumPipe
    • ParseFloatPipe
    • ParseFilePipe
  • Pipe针对某个参数生效,或者针对某个controller生效 或者全局生效 {provide: APP_PIPE, useClass: ValidationPipe}.

  • 不管Pipe, Guard, Interceptor还是最终的controller,过程中都可以抛出一些异常。如何对某种异常做出某种响应呢。这种异常到响应的映射也是一种通用逻辑。Nest提供了ExceptionFilter接口来实现。

6. ExceptionFilter

  • ExceptionFilter 可以对抛出的异常做处理,返回对应的响应:

  • Nest 内置了很多 http 相关的异常,都是 HttpException 的子类: BadRequestException UnauthorizedException NotFoundException ForbiddenException NotAcceptableException RequestTimeoutException ConflictException GoneException PayloadTooLargeException UnsupportedMediaTypeException UnprocessableException InternalServerErrorException NotImplementedException BadGatewayException ServiceUnavailableException GatewayTimeoutException

  • 自定义个异常处理类,实现ExceptionFilter接口。

  •  export class ForbiddenException implements HttpException {
         super("没有权限", HttpStatus.FORBIDDEN)
     }
    
     // Nest 通过这样的方式实现了异常到响应的对应关系,代码里只要抛出不同的异常,就会返回对应的响应,很方便。
    
  • ExceptionFilter 也可以选择全局生效或者某个路由生效或者某个Controller生效。

几种AOP的机制。

  • Middleware、Guard、Pipe、Interceptor、ExceptionFilter 都可以透明的添加某种处理逻辑到某个路由或者全部路由,这就是 AOP 的好处。