-
- [Gateway 不是跑在 Servlet 容器上的](#Gateway 不是跑在 Servlet 容器上的)
- [那 Gateway 用什么代替 Filter?](#那 Gateway 用什么代替 Filter?)
- 我们的经验是......
- [Gateway 中能写 Servlet Filter 吗?(补充版)](#Gateway 中能写 Servlet Filter 吗?(补充版))
-
- [1. Spring Boot 自带 Tomcat,为什么 Filter 不执行?](#1. Spring Boot 自带 Tomcat,为什么 Filter 不执行?)
- [2. Mono 和 Flux 是啥?为啥 Gateway 非要用它们?](#2. Mono 和 Flux 是啥?为啥 Gateway 非要用它们?)
- [3. Netty 是个什么东西?](#3. Netty 是个什么东西?)
- 总结一下

这个问题我之前也困惑过一阵子。因为刚接触 Spring Cloud Gateway 的时候,脑子里还带着以前用 Spring MVC 或者传统 Web 应用的思维------动不动就想加个 Filter 来处理请求日志、鉴权、跨域啥的。
但后来发现,Gateway 和传统的 Servlet 容器压根不是一个路子。今天就聊聊我的理解,顺便说说怎么在 Gateway 里"做类似 Filter 的事"。
Gateway 不是跑在 Servlet 容器上的
这点很重要。Spring Cloud Gateway 是基于 Reactor 模型构建的,底层用的是 Netty,而不是 Tomcat、Jetty 这些我们熟悉的 Servlet 容器。
而 javax.servlet.Filter(或者 jakarta.servlet.Filter)这个接口,是 Servlet 规范的一部分 。它只能在支持 Servlet 的容器里运行。所以,如果你硬要在 Gateway 项目里写一个 @Component 注解的 Filter,它根本不会被调用------因为 Netty 根本不认识它。
我一开始不信邪,写了下面这段代码:
java
@Component
public class MyServletFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("我在 Gateway 里打印这句话");
chain.doFilter(request, response);
}
}
结果?启动正常,但发任何请求,控制台都没输出。白忙活一场。
那 Gateway 用什么代替 Filter?
答案是:GlobalFilter 和 GatewayFilter。
这两兄弟才是 Gateway 世界的"过滤器"。它们不是 Servlet 那一套,而是响应式编程模型下的组件。
全局过滤器(GlobalFilter)
如果你希望对所有路由都生效,比如统一记录请求耗时、统一鉴权,那就写个 GlobalFilter:
java
@Component
public class LoggingGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long start = System.currentTimeMillis();
ServerHttpRequest request = exchange.getRequest();
System.out.println("请求来了: " + request.getURI());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long end = System.currentTimeMillis();
System.out.println("请求结束,耗时: " + (end - start) + "ms");
}));
}
@Override
public int getOrder() {
return -1; // 数字越小,优先级越高
}
}
注意这里返回的是 Mono<Void>,整个流程是异步非阻塞的。你不能像在 Servlet Filter 里那样直接 chain.doFilter() 就完事,得用 then()、flatMap() 这些操作符来组合逻辑。
局部过滤器(GatewayFilter)
如果只想对某个特定路由加逻辑,比如给 /api/user/** 加个 token 校验,可以在配置文件里配,也可以自定义 GatewayFilterFactory。
举个简单的例子,在 application.yml 里加个内置的过滤器:
yaml
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- AddRequestHeader=X-Source, gateway
这会给匹配的请求加上一个请求头。当然,你也可以自己写复杂的逻辑,比如解析 JWT、修改请求体等。
我们的经验是......
在团队迁移老项目到 Gateway 的过程中,最常踩的坑就是"想当然地用 Servlet 思维写代码"。比如:
- 试图在 Gateway 里用
HttpServletRequest------ 不行,得用ServerHttpRequest - 想用
ThreadLocal存用户信息 ------ 响应式环境下线程会切换,得用Context或Reactor Context - 直接调用阻塞的数据库或 HTTP 接口 ------ 会导致性能瓶颈,最好用
WebClient等非阻塞方式
所以我的建议是:一旦用了 Gateway,就彻底告别 Servlet 那套东西。别想着"能不能兼容",而是"怎么用新模型解决问题"。
Gateway 中能写 Servlet Filter 吗?(补充版)
上面讲了 Gateway 不能用 Servlet Filter,但我觉得有些问题还需要补充一下:"Spring Boot 不是自带 Tomcat 吗?为啥 Filter 不生效?" 、"Mono/Flux 到底是啥?" 、"Netty 又是个什么玩意儿?"
这些问题其实戳中了很多人迁移微服务网关时的认知盲区。我就结合我们踩过的坑,再唠一唠。
1. Spring Boot 自带 Tomcat,为什么 Filter 不执行?
这是个特别容易混淆的点!我一开始也纳闷:明明项目是 Spring Boot 啊,application.properties 里也没改啥,怎么 Filter 就不跑了?
关键在于:你启动的是不是"WebFlux"应用?
Spring Boot 从 2.x 开始,支持两种 Web 编程模型:
- Spring MVC :基于 Servlet,跑在 Tomcat/Jetty 上,用
DispatcherServlet处理请求。 - Spring WebFlux:响应式编程模型,可以跑在 Netty、Undertow,甚至也能跑在 Tomcat 上(但不用 Servlet 那套)。
而 Spring Cloud Gateway 是基于 WebFlux 构建的 。就算你没显式排除 Tomcat,只要引入了 spring-cloud-starter-gateway,Spring Boot 会自动切换成 WebFlux 模式 ,并且默认使用 Netty 作为内嵌服务器。
你可以试试看:启动一个纯 Gateway 项目,控制台会打印类似这样的日志:
Netty started on port 8080
而不是:
Tomcat started on port 8080
📌 重点来了 :即使你强行保留 Tomcat(比如加了
spring-boot-starter-web),只要用了 Gateway,它内部的路由和过滤逻辑依然是走 WebFlux + Reactor 的,不会经过 Servlet 容器的 Filter 链 。所以你写的@Component Filter根本没机会被调用。
我们的经验是:Gateway 项目里千万别同时引入 spring-boot-starter-web 和 spring-cloud-starter-gateway,否则会冲突,启动都可能失败。
2. Mono 和 Flux 是啥?为啥 Gateway 非要用它们?
简单说,Mono 和 Flux 是 Project Reactor 提供的两个核心类,用来做响应式编程(Reactive Programming)。
Mono<T>:表示 0 或 1 个元素的异步流。比如一个 HTTP 响应、一个用户对象。Flux<T>:表示 0 到 N 个元素的异步流。比如一个消息队列、一个文件流。
它们和传统的 List、User 有什么区别?最大的不同是:它们不代表"已经拿到的数据",而是"未来会拿到数据的承诺"。
举个例子:
java
// 传统方式(阻塞)
User user = userService.findById(1); // 线程卡在这儿,等数据库返回
// 响应式方式(非阻塞)
Mono<User> userMono = userService.findById(1); // 立刻返回,不等!
在 Gateway 的 GlobalFilter 里,你看到的这个方法签名:
java
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
意思就是:"我处理完这个请求后,会给你一个信号(Void),但我不阻塞线程"。整个链路靠 Mono 的组合(比如 .then(), .flatMap())来串联。
为什么 Gateway 要用这套?
因为网关要扛高并发。如果每个请求都占一个线程(像 Tomcat 那样),1万个并发就得开1万个线程,内存直接爆掉。而 Netty + Reactor 用少量线程就能处理几万甚至几十万连接------靠的就是"非阻塞 + 异步回调"。
所以,不是 Gateway "非要搞复杂",而是为了性能不得不这么干。
3. Netty 是个什么东西?
你可以把 Netty 理解为一个 高性能网络通信框架。它不依赖 Servlet 规范,直接跟操作系统底层的网络接口打交道(比如 Linux 的 epoll)。
Tomcat 是"面向请求-响应"的:来一个 HTTP 请求 → 分配一个线程 → 处理 → 返回 → 线程释放。
Netty 是"事件驱动"的:所有连接注册到 EventLoop(可以理解为一个线程池),当有数据可读/可写时,才触发回调。一个线程能同时管成百上千个连接。
Spring Cloud Gateway 选择 Netty,就是因为:
- 启动快
- 内存占用低
- 支持长连接、WebSocket、HTTP/2 等高级特性
- 天然契合响应式编程模型
打个比方:
- Tomcat 像是"每个顾客配一个服务员",人多了就忙不过来。
- Netty 像是"一个服务员盯所有桌子,谁举手就服务谁",效率高得多。
当然,Netty 学习曲线陡一点,调试也麻烦些。但对网关这种 I/O 密集型场景,它是目前 Java 生态里最成熟的选择。
总结一下
- 不能 在 Spring Cloud Gateway 中使用
Servlet Filter,因为它不跑在 Servlet 容器上。 - 要实现类似功能,请用
GlobalFilter或GatewayFilter。 - 别硬套旧习惯,响应式编程有它自己的套路,适应了其实也挺香。
- 如果你只是做个后台管理,QPS 几十,用 Spring MVC + Tomcat 完全够用,Filter 写起来也顺手。
- 但如果你要做 API 网关,面对的是成千上万的移动端或第三方调用,那 Gateway + Netty + Reactor 这套组合拳,就是更合适的选择。
我自己现在写 Gateway 项目,已经完全不用 Filter 这个词了,一说"过滤器",默认就是指 GlobalFilter。思维转过来之后,开发反而更清爽。
希望这篇碎碎念能帮到和我当初一样迷糊的朋友。