Gateway 中是否能寫 Servlet Filter
這個問題我之前也困惑過一陣子。因為剛接觸 Spring Cloud Gateway 的時候,腦子里還帶著以前用 Spring MVC 或者傳統(tǒng) Web 應(yīng)用的思維——動不動就想加個 Filter 來處理請求日志、鑒權(quán)、跨域啥的。
但后來發(fā)現(xiàn),Gateway 和傳統(tǒng)的 Servlet 容器壓根不是一個路子。今天就聊聊我的理解,順便說說怎么在 Gateway 里“做類似 Filter 的事”。
Gateway 不是跑在 Servlet 容器上的
這點很重要。Spring Cloud Gateway 是基于 Reactor 模型構(gòu)建的,底層用的是 Netty,而不是 Tomcat、Jetty 這些我們熟悉的 Servlet 容器。
而 javax.servlet.Filter(或者 jakarta.servlet.Filter)這個接口,是 Servlet 規(guī)范的一部分。它只能在支持 Servlet 的容器里運(yùn)行。所以,如果你硬要在 Gateway 項目里寫一個 @Component 注解的 Filter,它根本不會被調(diào)用——因為 Netty 根本不認(rèn)識它。
我一開始不信邪,寫了下面這段代碼:
@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);
}
}結(jié)果?啟動正常,但發(fā)任何請求,控制臺都沒輸出。白忙活一場。
那 Gateway 用什么代替 Filter?
答案是:GlobalFilter 和 GatewayFilter。
這兩兄弟才是 Gateway 世界的“過濾器”。它們不是 Servlet 那一套,而是響應(yīng)式編程模型下的組件。
全局過濾器(GlobalFilter)
如果你希望對所有路由都生效,比如統(tǒng)一記錄請求耗時、統(tǒng)一鑒權(quán),那就寫個 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("請求結(jié)束,耗時: " + (end - start) + "ms");
}));
}
@Override
public int getOrder() {
return -1; // 數(shù)字越小,優(yōu)先級越高
}
}注意這里返回的是 Mono<Void>,整個流程是異步非阻塞的。你不能像在 Servlet Filter 里那樣直接 chain.doFilter() 就完事,得用 then()、flatMap() 這些操作符來組合邏輯。
局部過濾器(GatewayFilter)
如果只想對某個特定路由加邏輯,比如給 /api/user/** 加個 token 校驗,可以在配置文件里配,也可以自定義 GatewayFilterFactory。
舉個簡單的例子,在 application.yml 里加個內(nèi)置的過濾器:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- AddRequestHeader=X-Source, gateway這會給匹配的請求加上一個請求頭。當(dāng)然,你也可以自己寫復(fù)雜的邏輯,比如解析 JWT、修改請求體等。
我們的經(jīng)驗是……
在團(tuán)隊遷移老項目到 Gateway 的過程中,最常踩的坑就是“想當(dāng)然地用 Servlet 思維寫代碼”。比如:
- 試圖在 Gateway 里用
HttpServletRequest—— 不行,得用ServerHttpRequest - 想用
ThreadLocal存用戶信息 —— 響應(yīng)式環(huán)境下線程會切換,得用Context或Reactor Context - 直接調(diào)用阻塞的數(shù)據(jù)庫或 HTTP 接口 —— 會導(dǎo)致性能瓶頸,最好用
WebClient等非阻塞方式
所以我的建議是:一旦用了 Gateway,就徹底告別 Servlet 那套東西。別想著“能不能兼容”,而是“怎么用新模型解決問題”。
Gateway 中能寫 Servlet Filter 嗎?(補(bǔ)充版)
上面講了 Gateway 不能用 Servlet Filter,但我覺得有些問題還需要補(bǔ)充一下:“Spring Boot 不是自帶 Tomcat 嗎?為啥 Filter 不生效?”、“Mono/Flux 到底是啥?”、“Netty 又是個什么玩意兒?”
這些問題其實戳中了很多人遷移微服務(wù)網(wǎng)關(guān)時的認(rèn)知盲區(qū)。我就結(jié)合我們踩過的坑,再嘮一嘮。
1. Spring Boot 自帶 Tomcat,為什么 Filter 不執(zhí)行?
這是個特別容易混淆的點!我一開始也納悶:明明項目是 Spring Boot 啊,application.properties 里也沒改啥,怎么 Filter 就不跑了?
關(guān)鍵在于:你啟動的是不是“WebFlux”應(yīng)用?
Spring Boot 從 2.x 開始,支持兩種 Web 編程模型:
- Spring MVC:基于 Servlet,跑在 Tomcat/Jetty 上,用
DispatcherServlet處理請求。 - Spring WebFlux:響應(yīng)式編程模型,可以跑在 Netty、Undertow,甚至也能跑在 Tomcat 上(但不用 Servlet 那套)。
而 Spring Cloud Gateway 是基于 WebFlux 構(gòu)建的。就算你沒顯式排除 Tomcat,只要引入了 spring-cloud-starter-gateway,Spring Boot 會自動切換成 WebFlux 模式,并且默認(rèn)使用 Netty 作為內(nèi)嵌服務(wù)器。
你可以試試看:啟動一個純 Gateway 項目,控制臺會打印類似這樣的日志:
Netty started on port 8080
而不是:
Tomcat started on port 8080
?? 重點來了:即使你強(qiáng)行保留 Tomcat(比如加了
spring-boot-starter-web),只要用了 Gateway,它內(nèi)部的路由和過濾邏輯依然是走 WebFlux + Reactor 的,不會經(jīng)過 Servlet 容器的 Filter 鏈。所以你寫的@Component Filter根本沒機(jī)會被調(diào)用。
我們的經(jīng)驗是:Gateway 項目里千萬別同時引入 spring-boot-starter-web 和 spring-cloud-starter-gateway,否則會沖突,啟動都可能失敗。
2. Mono 和 Flux 是啥?為啥 Gateway 非要用它們?
簡單說,Mono 和 Flux 是 Project Reactor 提供的兩個核心類,用來做響應(yīng)式編程(Reactive Programming)。
Mono<T>:表示 0 或 1 個元素的異步流。比如一個 HTTP 響應(yīng)、一個用戶對象。Flux<T>:表示 0 到 N 個元素的異步流。比如一個消息隊列、一個文件流。
它們和傳統(tǒng)的 List、User 有什么區(qū)別?最大的不同是:它們不代表“已經(jīng)拿到的數(shù)據(jù)”,而是“未來會拿到數(shù)據(jù)的承諾”。
舉個例子:
// 傳統(tǒng)方式(阻塞) User user = userService.findById(1); // 線程卡在這兒,等數(shù)據(jù)庫返回 // 響應(yīng)式方式(非阻塞) Mono<User> userMono = userService.findById(1); // 立刻返回,不等!
在 Gateway 的 GlobalFilter 里,你看到的這個方法簽名:
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
意思就是:“我處理完這個請求后,會給你一個信號(Void),但我不阻塞線程”。整個鏈路靠 Mono 的組合(比如 .then(), .flatMap())來串聯(lián)。
為什么 Gateway 要用這套?
因為網(wǎng)關(guān)要扛高并發(fā)。如果每個請求都占一個線程(像 Tomcat 那樣),1萬個并發(fā)就得開1萬個線程,內(nèi)存直接爆掉。而 Netty + Reactor 用少量線程就能處理幾萬甚至幾十萬連接——靠的就是“非阻塞 + 異步回調(diào)”。
所以,不是 Gateway “非要搞復(fù)雜”,而是為了性能不得不這么干。
3. Netty 是個什么東西?
你可以把 Netty 理解為一個 高性能網(wǎng)絡(luò)通信框架。它不依賴 Servlet 規(guī)范,直接跟操作系統(tǒng)底層的網(wǎng)絡(luò)接口打交道(比如 Linux 的 epoll)。
Tomcat 是“面向請求-響應(yīng)”的:來一個 HTTP 請求 → 分配一個線程 → 處理 → 返回 → 線程釋放。
Netty 是“事件驅(qū)動”的:所有連接注冊到 EventLoop(可以理解為一個線程池),當(dāng)有數(shù)據(jù)可讀/可寫時,才觸發(fā)回調(diào)。一個線程能同時管成百上千個連接。
Spring Cloud Gateway 選擇 Netty,就是因為:
- 啟動快
- 內(nèi)存占用低
- 支持長連接、WebSocket、HTTP/2 等高級特性
- 天然契合響應(yīng)式編程模型
打個比方:
- Tomcat 像是“每個顧客配一個服務(wù)員”,人多了就忙不過來。
- Netty 像是“一個服務(wù)員盯所有桌子,誰舉手就服務(wù)誰”,效率高得多。
當(dāng)然,Netty 學(xué)習(xí)曲線陡一點,調(diào)試也麻煩些。但對網(wǎng)關(guān)這種 I/O 密集型場景,它是目前 Java 生態(tài)里最成熟的選擇。
總結(jié)一下
- 不能在 Spring Cloud Gateway 中使用
Servlet Filter,因為它不跑在 Servlet 容器上。 - 要實現(xiàn)類似功能,請用
GlobalFilter或GatewayFilter。 - 別硬套舊習(xí)慣,響應(yīng)式編程有它自己的套路,適應(yīng)了其實也挺香。
- 如果你只是做個后臺管理,QPS 幾十,用 Spring MVC + Tomcat 完全夠用,F(xiàn)ilter 寫起來也順手。
- 但如果你要做 API 網(wǎng)關(guān),面對的是成千上萬的移動端或第三方調(diào)用,那 Gateway + Netty + Reactor 這套組合拳,就是更合適的選擇。
我自己現(xiàn)在寫 Gateway 項目,已經(jīng)完全不用 Filter 這個詞了,一說“過濾器”,默認(rèn)就是指 GlobalFilter。思維轉(zhuǎn)過來之后,開發(fā)反而更清爽。
到此這篇關(guān)于Gateway 中能寫 Servlet Filter 嗎?的文章就介紹到這了,更多相關(guān)Gateway 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA運(yùn)行SpringBoot項目的超詳細(xì)步驟截圖
在當(dāng)前的開發(fā)中Spring Boot開發(fā)框架已經(jīng)成為主流,下面這篇文章主要給大家介紹了關(guān)于IDEA運(yùn)行SpringBoot項目的超詳細(xì)步驟截圖,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11
IntelliJ IDEA設(shè)置Tabs實現(xiàn)同時打開多個文件且分行顯示
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA設(shè)置Tabs實現(xiàn)同時打開多個文件且分行顯示,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-10-10
SpringBoot整合Flink CDC實現(xiàn)實時追蹤mysql數(shù)據(jù)變動
我們將整合Spring Boot和Apache Flink CDC(Change Data Capture)來實現(xiàn)實時數(shù)據(jù)追蹤,下面是一個基本的實踐流程代碼,包括搭建Spring Boot項目、整合Flink CDC以及實現(xiàn)數(shù)據(jù)變動的實時追蹤,需要的朋友可以參考下2024-07-07
Mybatis批量插入大量數(shù)據(jù)的最優(yōu)方式總結(jié)
批量插入功能是我們?nèi)粘9ぷ髦斜容^常見的業(yè)務(wù)功能之一,下面這篇文章主要給大家總結(jié)介紹了關(guān)于Mybatis批量插入大量數(shù)據(jù)的幾種最優(yōu)方式,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03

