这个问题我之前也困惑过一阵子。因为刚接触 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 根本不认识它。
我一开始不信邪,写了下面这段代码:
@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:
@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 里加个内置的过滤器:
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 有什么区别?最大的不同是:它们不代表“已经拿到的数据”,而是“未来会拿到数据的承诺”。
举个例子:
// 传统方式(阻塞) user user = userservice.findbyid(1); // 线程卡在这儿,等数据库返回 // 响应式方式(非阻塞) mono<user> usermono = userservice.findbyid(1); // 立刻返回,不等!
在 gateway 的 globalfilter 里,你看到的这个方法签名:
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。思维转过来之后,开发反而更清爽。
到此这篇关于gateway 中能写 servlet filter 吗?的文章就介绍到这了,更多相关gateway 内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论