Nest 应用程序按照我们称之为请求生命周期的序列处理请求并生成响应。由于中间件、管道、守卫和拦截器的使用,在请求生命周期中跟踪特定代码段的执行位置可能具有挑战性,特别是当全局、控制器级别和路由级别的组件介入时。通常情况下,请求依次经过中间件到守卫,再到拦截器,然后到管道,最后在返回路径上再次经过拦截器(当生成响应时)。
中间件按特定顺序执行。首先,Nest 运行全局绑定的中间件(如使用 app.use
绑定的中间件),然后运行模块绑定中间件 ,这些中间件是根据路径确定的。中间件按照它们被绑定的顺序依次运行,类似于 Express 中中间件的工作方式。对于跨不同模块绑定的中间件,绑定到根模块的中间件将首先运行,然后中间件将按照模块被添加到 imports 数组的顺序运行。
守卫的执行顺序是从全局守卫开始,接着是控制器守卫,最后是路由守卫。与中间件类似,守卫按照绑定顺序依次运行。例如:
Guard1
会在 Guard2
之前执行,而二者都会在 Guard3
之前执行。
info 注意 当讨论全局绑定与控制器或本地绑定的区别时,关键在于守卫(或其他组件)绑定的位置。如果使用
app.useGlobalGuard()
或通过模块提供该组件,则为全局绑定。否则,装饰器位于控制器类前时绑定到控制器,位于路由声明前时则绑定到路由。
拦截器在很大程度上遵循与守卫相同的模式,但有一点不同:由于拦截器返回 RxJS Observables,这些可观察对象将以先进后出的方式解析。因此,入站请求将经过标准的全局、控制器、路由级别的解析流程,但请求的响应端(即从控制器方法处理程序返回后)将从路由到控制器再到全局进行解析。此外,管道、控制器或服务抛出的任何错误都可以在拦截器的 catchError
操作符中捕获。
管道遵循从全局到控制器再到路由绑定的标准顺序,对于 @UsePipes()
参数同样采用先进先出的原则。然而,在路由参数级别,如果有多个管道运行,它们将按照从最后一个带管道的参数到第一个参数的顺序执行。这也适用于路由级别和控制器级别的管道。例如,假设我们有如下控制器:
那么 GeneralValidationPipe
会先对 query
执行验证,然后是 params
,接着是 body
对象,最后才会执行 RouteSpecificPipe
(遵循同样的顺序)。如果存在任何参数特定的管道,它们将在控制器和路由级别的管道之后运行(同样是从最后一个参数到第一个参数)。
过滤器是唯一不优先解析全局组件的部分。相反,过滤器会从最低层级开始解析,这意味着执行首先从路由绑定的过滤器开始,然后是控制器级别,最后才是全局过滤器。需要注意的是异常无法在过滤器之间传递;如果路由级别的过滤器捕获了异常,控制器或全局级别的过滤器就无法捕获同一个异常。要实现类似效果的唯一方法是使用过滤器之间的继承关系。
info 提示 过滤器仅在请求过程中发生未捕获异常时才会执行。已捕获的异常(例如通过
try/catch
捕获的异常)不会触发异常过滤器。一旦遇到未捕获异常,请求将跳过剩余生命周期直接进入过滤器处理阶段。
通常情况下,请求生命周期遵循以下流程: