Middleware vs Interceptor:区别与适用场景
Middleware(中间件)和Interceptor(拦截器)。它们都是 AOP(面向切面编程)思想的实现,都能在请求处理前后插入逻辑,但很多人搞不清什么时候该用哪个。今天我就结合文件里的内容,把它们的区别和适用场景彻底讲清楚。
先回顾一下它们是什么
Middleware 是 Express 里的老概念,Nest 直接继承了过来。它可以在请求到达路由之前执行一些通用逻辑,比如日志记录、请求体解析、跨域设置等。中间件通过 use 方法定义,可以访问请求和响应对象,并且必须调用 next() 把控制权交出去。它可以在全局注册,也可以在模块里针对特定路由配置。
Interceptor 是 Nest 自己封装的一套机制,基于 RxJS 实现。它可以在控制器方法执行前后添加逻辑,甚至可以修改返回值或捕获异常。拦截器需要实现 NestInterceptor 接口,通过 intercept 方法操作 next.handle() 返回的 Observable 流。它也可以在方法、控制器或全局级别启用。
核心区别在哪?
虽然两者都能在请求前后“插一脚”,但它们的能力和定位完全不同。
1. 执行位置和能拿到的信息
- Middleware 是最外层的,它只拿到原生的请求和响应对象。它不知道这个请求最终会交给哪个控制器、哪个方法处理,也不知道这个控制器或方法上有什么元数据(比如你用
@SetMetadata贴的自定义数据)。 - Interceptor 在请求已经被路由匹配之后执行,所以它可以通过
ExecutionContext拿到目标控制器类(context.getClass())和处理函数(context.getHandler())。这意味着拦截器可以结合Reflector读取该处理函数上的元数据,实现更精细的控制。
文件里特别提到:“interceptor 可以拿到调用的 controller 和 handler,middleware 不可以。” 这是它们一个非常关键的差异。
2. 处理响应的方式
- Middleware 处理完请求后,通常直接调用
next()让请求继续,或者直接结束响应。它无法对控制器返回的数据做进一步处理(除非你重写响应方法,但那样很 hack)。 - Interceptor 基于 RxJS,可以拿到控制器返回的
Observable,然后用各种操作符(如tap、map、catchError、timeout)对响应流进行变换。比如你可以用map把所有返回值包装成统一的{ code, data, message }格式,或者用timeout设置超时处理。
3. 依赖注入
- Middleware 如果在模块里通过
configure方法注册,是可以享受依赖注入的(因为它在 IoC 容器里)。但如果直接在main.ts里用app.use()注册,就无法注入其他 provider。 - Interceptor 只要是正常的 provider(在模块的
providers里声明),就可以通过构造函数注入其他依赖。即使是全局拦截器,如果通过APP_INTERCEPTOR令牌注册,也能注入依赖。这点上拦截器更灵活。
4. 适用的场景
文件里有一句话总结得很到位:“interceptor 更适合处理与具体业务相关的逻辑,而 middleware 适合更通用的处理逻辑。”
- Middleware 适合那些和具体业务无关、纯粹是 HTTP 层面的预处理:比如记录每个请求的日志、设置响应头、解析 cookie、静态文件服务等。因为这些逻辑不关心业务,只关心请求和响应本身。
- Interceptor 适合在业务层面进行横切:比如统一响应格式、缓存处理、记录接口耗时(可以结合
tap记录日志)、权限校验(虽然权限通常用 Guard,但拦截器也可以做)、事务管理等。因为这些逻辑往往需要知道是哪个方法在处理,并且可能要根据方法的元数据来决定行为。
举个例子让你更清楚
假设我们有一个需求:记录每个接口的耗时,并打印出来。
如果用中间件实现,你能做的只是在请求进来时记录开始时间,然后等响应结束后再记录结束时间。但中间件没法知道响应什么时候结束,除非你重写 res.end 方法,这非常复杂且容易出错。
用拦截器就简单多了:
@Injectable()
export class TimingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now();
return next.handle().pipe(
tap(() => {
const duration = Date.now() - start;
console.log(`接口耗时:${duration}ms`);
}),
);
}
}因为拦截器能拿到 next.handle() 返回的 Observable,可以很自然地在流结束时执行逻辑。
再比如,你想给某些接口加上缓存,只有加了 @Cacheable() 装饰器的方法才走缓存。中间件完全不知道方法上的装饰器,但拦截器可以:
@Injectable()
export class CacheInterceptor implements NestInterceptor {
constructor(private reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const cacheable = this.reflector.get<boolean>('cacheable', context.getHandler());
if (!cacheable) {
return next.handle();
}
// 缓存逻辑...
}
}这种基于元数据的能力,中间件是做不到的。
总结:什么时候用哪个?
- 如果你需要处理的逻辑是 HTTP 层通用的,比如 CORS、日志、请求体解析,就用 Middleware。
- 如果你需要处理的逻辑与 业务或控制器方法紧密相关,比如统一响应格式、缓存、事务、耗时统计,就用 Interceptor。
- 如果你需要读取方法上的自定义元数据,必须用 Interceptor(或 Guard)。
- 如果你需要对响应流进行变换或超时控制,必须用 Interceptor(基于 RxJS)。
两者并不冲突,可以共存。请求会先经过中间件,然后到守卫,然后到拦截器,再到管道,最后到控制器。所以你可以把通用逻辑放中间件,把业务相关逻辑放拦截器。

Comments | NOTHING