记录我的NestJS探究历程(五)

在上一篇文章中分析了NestJS的路由处理流程之后,本文开始分析NestJS中间件知识点。

初探NestJS的运行原理之中间件

先看一下,我们在NestJS编写一个中间件的代码,以下是我在前文提到过的全局注入Request对象的中间件的例子。

ts 复制代码
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { nanoid } from 'nanoid';
import { SingletonLoggerService } from '../services/logger/singleton-logger.service';

@Injectable()
export class TraceMiddleware implements NestMiddleware {
  use(req: Request & { traceId: string }, res: Response, next: NextFunction) {
    const uuid = nanoid(32);
    req.traceId = uuid;
    // 在中间件中注入request对象,可以使得每次打印的日志都有request上下文信息
    SingletonLoggerService.getInstance().setRequest(req);
    next();
  }
}

然后是绑定中间件:

ts 复制代码
// 省略了很多无关的代码
@Module({})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    // 注册中间件
    consumer.apply(TraceMiddleware).forRoutes('*');
  }
}

在之前的文章,我们说过了,NestJS默认使用的是Express提供的更底层的Http能力,于是我们首先看一下@nestjs/platform-express这个包里面干了一些啥。

核心方法就是这个createMiddlewareFactory: 紧接着,我们还是利用跟踪堆栈的信息的办法来查看NestJS初始化我们编写的中间件的过程。 首先是我们调用NestJS提供的init方法,这个在之前的文章就已经提到过了(在NestJS启动过程中,我们调用init方法之前,对中间件管理的MiddlewareModule模块就已经事先初始化了,因为它的定义在NestApplication的构造器中) 在这之前,IoC容器已经完成了模块和模块依赖的内容创建,我们已经可以通过模块拿到其对应的实例了。 此刻MiddlewareModule就开始去读取中间件了。 在这个位置,我们回想一下,我们之前写在主模块里面的configure方法,在此刻被调用了,中间件注册主线流程就完成了。 不过千万别捡了芝麻丢了西瓜,如果爱情有这么简单,那么就不会有那么多单身的人了,哈哈哈,我们需要详细探究一下NestJS的中间件是如何跟Express绑定上去的,所以得看一下MiddlewareBuilder做了什么事儿(还是像我们上文说的那个意思,现在中间件只通过了面试,甚至都没有拿到offer,它还没法工作)。

MiddlewareBuilder里面找了一圈,看起来并没有做什么事儿,是否遗漏了什么地方,我们尝试先往回找。 在NestJS的init方法里面,我们可能看到了它注册的链路,先顺着这个链路找一下,如果找不到的话到时候再回过来看看吧。 顺着这个调用链,可以追踪到MiddlewareModule的registerMiddleware方法,看起来就是这儿了,运气还不错呀。 感觉快到和Express的绑定逻辑了,我已经很迫不及待啦。 恭喜我们,已经到达终点了,在之前的文章我们已经知道了applicationRef就是我们注入进来的Express Adaptor的实例,这个位置的逻辑就跟我们平时写Expressuse函数一样的操作了。 我并不是刻意的避重就轻,我们需要重视这个registerHandler函数在之前的调用,(因为它体现了框架设计者的严谨,这也是我们学习源码的动力所在),所以现在回过头来分析它那一串很复杂的调用含义。 在代理里面绑定仅针对中间件的过滤器 上述流程,仅仅处理的是class类型的中间件,NestJS是在这个位置处理的函数式中间件:

其实跟class类型的中间件差不多,NestJS统一了函数式中间件,就是先要进行一遍转换,最终都转换成了class中间件再处理。

至此,我们大概已经完全明白了NestJS中间件的流程。

我们可以通过以上内容的学习得出某些结论:

  • NestJS的中间件可以有很多个,并且并不是说我们一定要在主模块注册中间件。
  • 如果模块仅针对自己的中间件,建议在每个模块内部编写中间件,而不要全部都写到主模块上,会让主模块很臃肿。
  • 在写过滤器的时候,需要尽量谨慎的使用通配符(或者使用命名空间,比如/api/*)这种,因为通配符会应用到别的模块,可能这是你非预期的,避免造成潜在的问题。

除此之外,我们还能学到一点儿编程技巧,比如为什么它能这样链式调用呢:

ts 复制代码
@Module({
  imports: [],
  controllers: [DemoController],
  providers: [],
})
export class DemoModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(DemoMiddleware)
      .forRoutes('/demo')
      .apply((req, res, next) => {
        console.log('hello world');
        debugger;
        next();
      })
      .forRoutes('*');
  }
}

它的源码的关键部分如下:

ts 复制代码
export class MiddlewareBuilder implements MiddlewareConsumer {
  public apply(
    ...middleware: Array<Type<any> | Function | any>
  ): MiddlewareConfigProxy {
    return new MiddlewareBuilder.ConfigProxy(
      this,
      flatten(middleware),
      this.routeInfoPathExtractor,
    );
  }
  
  // 省略了一些无关的代码

  private static readonly ConfigProxy = class implements MiddlewareConfigProxy {

    constructor(
      private readonly builder: MiddlewareBuilder,
      private readonly middleware: Array<Type<any> | Function | any>,
      private routeInfoPathExtractor: RouteInfoPathExtractor,
    ) {
        // 这个位置的写法就相当于 this.middleware = middleware
    }

    public forRoutes(
      ...routes: Array<string | Type<any> | RouteInfo>
    ): MiddlewareConsumer {
      // 进行了某些操作
      return this.builder;
    }
  };
}

middleware这个变量,这个位置有点儿像哨兵(sentinel)的味道了,如果你钻研过某些开源库的话,这种编程手段在Vue2源码中实现双向绑定也是运用了这种方式。每当我们调用apply方法之后,哨兵数组上就绑定好了我们添加的中间件,当我们调用foRoutes方法的时候,消费掉哨兵数组上的中间件,下次调用的时候,哨兵数组又被赋值为下一次的中间件列表,从而可以实现优雅的链式调用。

在下一节内容,我们将开始分析NestJS的过滤器,拦截器,守卫等内容的技术细节,敬请期待!

相关推荐
alikami10 分钟前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
wakangda41 分钟前
React Native 集成原生Android功能
javascript·react native·react.js
吃杠碰小鸡44 分钟前
lodash常用函数
前端·javascript
丰云1 小时前
一个简单封装的的nodejs缓存对象
缓存·node.js
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼1 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
一个处女座的程序猿O(∩_∩)O1 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
User_undefined1 小时前
uniapp Native.js原生arr插件服务发送广播到uniapp页面中
android·javascript·uni-app
麦兜*1 小时前
轮播图带详情插件、uniApp插件
前端·javascript·uni-app·vue
陈大爷(有低保)1 小时前
uniapp小案例---趣味打字坤
前端·javascript·vue.js