前言、Spring Cloud Gateway 與 Web 依賴沖突
<!-- 下面兩個依賴不能同時使用 --><!-- Gateway 組件 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId><version>2.0.0.RELEASE</version></dependency><!-- springboot-web組件 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
1. 現象
在使用
spring-cloud-starter-gateway
時,如果項目同時引入了spring-boot-starter-web
(傳統的 Servlet Web 依賴),兩者會沖突。主要表現為:啟動時端口被占用、DispatcherServlet 與 Netty 服務器沖突,導致服務無法正常啟動。
2. 原因
Spring Cloud Gateway 基于 Spring WebFlux,使用的是 Netty 作為底層服務器(響應式非阻塞)。
spring-boot-starter-web
是基于傳統的 Servlet,使用的是 Tomcat 容器(阻塞模型)。兩種模型底層不兼容,不能同時啟動。
3. 解決方案
如果要使用 Gateway,項目中應使用
spring-boot-starter-webflux
替代spring-boot-starter-web
。保證只用一個 Web 服務器容器(Netty)。
不能同時引入
spring-boot-starter-web
和spring-cloud-starter-gateway
。4. 注意事項
如果項目有控制器需要兼容傳統 MVC,推薦拆分服務:
API 網關用 Spring Cloud Gateway + WebFlux
后端服務用 Spring MVC (
spring-boot-starter-web
)避免同時使用兩個 Web 依賴。
一、三種路由配置方式詳解與對比:自動發現、靜態 URI 與服務名路由(lb)
? 1、三種 Gateway 配置方式說明
1?? 自動服務發現路由(基于注冊中心,比如 Nacos)
spring:cloud:gateway:discovery:locator:enabled: true
? 說明:
開啟后 Gateway 會自動將注冊中心(如 Nacos)中注冊的服務生成路由。
不需要手動寫
routes
。訪問路徑格式固定為:
http://網關地址/服務名/xxx
實際轉發到的服務為注冊中心中對應服務實例
? 適用場景:
微服務較多,想簡化網關配置。
不需要定制復雜路由規則。
? 是否用到 Nacos:
? 是的,必須使用 Nacos / Eureka 等注冊中心,才能自動發現服務。
2?? 手動配置路由(靜態 URI)
spring:cloud:gateway:routes:- id: baiduuri: http://www.baidu.com/predicates:- Path=/baidu/**
? 說明:
手動指定目標 URI。
uri
是靜態地址,不能做服務發現。
Path=/baidu/**
表示匹配/baidu/*
的請求會被轉發到http://www.baidu.com
? 適用場景:
轉發到外部服務,如第三方接口、靜態頁面。
不使用服務發現(非微服務調用)。
? 是否用到 Nacos:
? 不需要 Nacos。
3?? 手動配置路由(基于服務名,使用負載均衡)
?基于服務名,就必須用lb:
spring:cloud:gateway:routes:- id: user-serviceuri: lb://user-service/predicates:- Path=/user/**
? 說明:
lb://user-service
表示通過 注冊中心獲取 user-service 實例,并使用負載均衡訪問。
lb:
是 load balancer 的前綴,Spring Cloud Gateway 會自動接入負載均衡策略。? 適用場景:
想要自定義路由規則 + 使用 Nacos 服務發現。
比如
/user/**
映射到user-service
微服務。? 是否用到 Nacos:
? 是的,需要注冊中心。
注意:
????????Spring Cloud Gateway 使用的是 Spring Cloud LoadBalancer,默認策略是
RoundRobinLoadBalancer
(輪詢算法)。如果想使用隨機就去定義下列代碼即可@Configuration public class ProducerLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,LoadBalancerClientFactory factory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);//隨機return new RandomLoadBalancer(factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);//輪詢return new RoundRobinLoadBalancer(factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);return new MyCustomLoadBalancer(); // 自定義負載均衡策略} //也可以自定義負載均衡策略 public class MyCustomLoadBalancer implements ReactorServiceInstanceLoadBalancer {private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;private final String serviceId;public MyCustomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> provider, String serviceId) {this.serviceInstanceListSupplierProvider = provider;this.serviceId = serviceId;}@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {return serviceInstanceListSupplierProvider.getIfAvailable().get().next().map(serviceInstances -> {// TODO: 在這里寫你的選擇邏輯(比如權重、元數據等)if (serviceInstances.isEmpty()) {return new EmptyResponse();}// 簡單示例:隨機ServiceInstance instance = serviceInstances.get(ThreadLocalRandom.current().nextInt(serviceInstances.size()));return new DefaultResponse(instance);});} }}
??想了解更多負載均衡以及如果我不同服務想用不同負載均衡策略怎么辦?
? ? ? ? 可以去看筆者的這兩篇博客這里不多贅述
分布式微服務--萬字詳解 微服務的各種負載均衡全場景以注意點-CSDN博客
分布式微服務--Ribbon 與 Spring Cloud LoadBalancer 區別 (五)-CSDN博客
? 2、三種配置方式對比總結
配置方式 依賴 Nacos 是否動態發現 路由配置方式 場景 discovery.locator.enabled ? 是 ? 自動發現服務 不寫 routes 微服務多,簡化配置 routes + 靜態 URI(http://) ? 否 ? 靜態目標 手動寫 routes 轉發外部接口 routes + 動態 URI(lb://) ? 是 ? 使用服務名 手動寫 routes 自定義規則 + 服務發現
? 3、常見配置誤區提醒
開啟
discovery.locator.enabled: true
后,如果你寫了routes
,它們可以 共存,但routes
優先級更高。
lb://xxx
必須啟用服務發現并引入spring-cloud-starter-loadbalancer
或等效功能(大多數 starter 默認引入)。若使用 Nacos,需要引入如下依賴:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
二、uri中結尾加不加/的區別
🌐 Gateway 路由中
uri
是否加/
的影響📌 背景示例
spring:cloud:gateway:routes:- id: baiduuri: http://www.baidu.compredicates:- Path=/baidu/**
訪問
http://localhost:8080/baidu/index
時:
配置方式 實際轉發地址 uri: http://www.baidu.com
(無/
)http://www.baidu.com/baidu/index
uri: http://www.baidu.com/
(有/
)http://www.baidu.com/index
? 原因說明
請求
/baidu/index
命中路由,剩余路徑會拼接到uri
后。如果
uri
沒有/
,會直接拼接整個/baidu/index
。如果
uri
有/
,則拼接去掉前綴后的部分(更合理)。
🛠 推薦寫法(最佳實踐)
使用
StripPrefix
過濾器去除前綴:spring:cloud:gateway:routes:- id: baiduuri: http://www.baidu.com/predicates:- Path=/baidu/**filters:- StripPrefix=1
效果:
請求
/baidu/index
實際轉發:
http://www.baidu.com/index
📝 總結建議
配置項 建議用法 說明 uri
結尾加 /
避免重復路徑拼接 StripPrefix
根據路徑前綴設置 去掉前綴,保持后端路徑整潔 注意:為什么看起來uri結尾有/時,加不加filters:- StripPrefix=1,效果一樣呢?
在這個例子中,兩種配置的轉發結果一致,是因為:
uri
?帶斜杠時,Gateway 的默認行為就是去除斷言中定義的前綴(/baidu
);StripPrefix=1
?在這里的作用與默認行為重合(都是去除?/baidu
)。在下面例子就有所不同了
例:多段前綴(如?
/api/baidu/**
)predicates:- Path=/api/baidu/**# 無 StripPrefix + uri 帶斜杠:去除完整前綴 /api/baidu,轉發到 http://www.baidu.com/s?wd=test # 有 StripPrefix=1 + uri 帶斜杠:僅去除第一段 /api,轉發到 http://www.baidu.com/baidu/s?wd=test
總結
- 在你的特定例子中,兩種配置效果表面相同(轉發地址一致);
- 但本質邏輯不同:前者依賴?
uri
?帶斜杠的默認行為,后者依賴顯式的?StripPrefix
?過濾器;- 最佳實踐:推薦使用?
StripPrefix
?過濾器,因為它更直觀、可控,避免依賴?uri
?斜杠的隱式規則(尤其在復雜路徑場景下)。🧪 三、支持的斷言(Predicates)常用類型
Predicate 示例 說明 Path /user/**
請求路徑匹配 Method GET, POST
請求方法匹配 Header "X-Request-Id", "^\d+$"
請求頭匹配 Host **.example.com
Host 匹配 After/Before/Between 時間限制訪問 支持 ISO8601 時間格式
🧩 四、支持的過濾器(Filters)常用類型
Filter 示例 說明 AddRequestHeader AddRequestHeader=X-Request-color, blue
添加請求頭 AddResponseHeader 同上 添加響應頭 RewritePath RewritePath=/foo/(?<segment>.*), /$\{segment}
路徑重寫 StripPrefix StripPrefix=1
去掉路徑前綴 Hystrix name=myHystrixCommand fallbackUri=forward:/fallback
熔斷處理(Spring Cloud Netflix 需要依賴) 過濾器小例子:
????????下面的代碼通過A使用服務名和Gateway調用到B的時候,會在這個請求添加請求頭,并且B中可以獲取這個請求頭的值
調用方A:(只需要在yml中添加?AddRequestHeader=X-Request-color,red)
server:port: 7009 spring:application:name: boyatop-gatewaycloud:nacos:discovery:server-addr: 127.0.0.1:8848gateway:routes:- id: memberuri: lb://member1predicates:- Path=/member/**filters: #下面是過濾器的配置- StripPrefix=1- AddRequestHeader=X-Request-color,red #添加請求頭
被調用方B:
//yml server:port: 8081 spring:application:name: membercloud:nacos:discovery:server-addr: 127.0.0.1:8848 //啟動類 @SpringBootApplication public class MemberApplication {public static void main(String[] args) {SpringApplication.run(MemberApplication.class, args);} } //Controller類 @RestController @Slf4j public class headerController{@RequestMapping("/get")public String get(){return "hhh8081";}@GetMapping("/testgateway")public String testGateway(HttpServletRequest request) throws Exception {log.info("gateWay獲取請求頭X-Request -color:" +request.getHeader("X-Request-color"));return "success";}@GetMapping("/testgateway2")public String testGateway(@RequestHeader("X-Request-color") String color) throws Exception {log.info("gateWay獲取請求頭X-Request -color:"+color);return "success";} }
🔍五、自定義過濾器(GlobalFilter)
一、兩者概念區別
對比項 自定義過濾器(GatewayFilter) 全局過濾器(GlobalFilter) 作用范圍 只作用于配置中的指定路由 作用于所有請求(全局) 實現接口 GatewayFilter
+AbstractGatewayFilterFactory
GlobalFilter
+Ordered
是否需要 yml 配置 ? 是,必須在 application.yml
的路由中配置? 否,不需要配置,自動生效 應用場景 針對某個路由的參數處理、Header 增加等 鑒權、日志、異常統一處理、限流等 觸發機制 僅當匹配到某條路由才執行 每個請求都會執行
二、自定義過濾器(AbstractGatewayFilterFactory)示例
1?? 編寫自定義過濾器類:
@Component public class AddHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<AddHeaderGatewayFilterFactory.Config> {public static class Config {private String name;private String value;// getter / setter}public AddHeaderGatewayFilterFactory() {super(Config.class);}@Overridepublic GatewayFilter apply(Config config) {return (exchange, chain) -> {// 添加請求頭exchange.getRequest().mutate().header(config.name, config.value).build();return chain.filter(exchange);};} }
2?? yml 中使用該過濾器:
spring:cloud:gateway:routes:- id: user_routeuri: lb://user-servicepredicates:- Path=/user/**filters:#這個名稱必須與AddHeaderGatewayFilterFactory所匹配否則匹配不上#也就是說自定義攔截器叫HhGatewayFilterFactory#下面也要寫成Hh=name,value - AddHeader=name,value
3. 自定義過濾器和全局過濾器使用場景對比
需求 / 功能 推薦使用類型 說明 針對某個接口改請求頭、參數 自定義過濾器 精準控制某條路由 日志記錄(所有請求) 全局過濾器 統一處理 全局 token 鑒權 全局過濾器 所有路由都校驗 自定義 header 注入 自定義過濾器 某個接口單獨處理 異常處理 / 限流 全局過濾器 通用邏輯 🔍五、全局過濾器(GlobalFilter)
? 不需要寫在 yml 中,所有請求默認都會走這個過濾器。
@Component public class AuthGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String token = exchange.getRequest().getHeaders().getFirst("token");if (StringUtils.isEmpty(token)) {exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}return chain.filter(exchange);}@Overridepublic int getOrder() {return -1; // 越小越先執行} }
這個全局過濾器實現了:
檢查請求頭中是否包含
token
如果沒有,直接返回 401 狀態碼
? 實現
Ordered
接口5.1、核心區別
是否實現 Ordered 接口 執行順序可控 說明 ? 未實現 ? 不可控(默認順序) 無法確定具體執行順序 ? 實現 ? 可控 可通過 getOrder()
指定優先級
5.2、
Ordered
接口作用
Ordered
是 Spring 提供的一個接口:
用于定義組件(如 Filter、Interceptor 等)的執行順序;
數字越小,優先級越高(越早執行)。
5.3、代碼示例對比
5.3.1 不實現 Ordered(默認順序)
@Component public class AuthGlobalFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 過濾邏輯return chain.filter(exchange);} }
執行順序默認;
多個過濾器順序無法手動控制。
5.3.2?實現 Ordered(手動控制順序)
@Component public class AuthGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 過濾邏輯return chain.filter(exchange);}@Overridepublic int getOrder() {return -1; // 數字越小,優先級越高} }
可以明確控制執行順序;
常用于權限驗證、日志記錄等需要優先執行的邏輯。
5.4、
getOrder()
的優先級說明
數值 優先級說明 常用場景 -1 高優先級 權限校驗 0 默認優先級 日志記錄等常規 1 比默認稍晚 后置處理 100 明顯靠后 最后處理邏輯
? 實踐建議:
需要控制順序 ? 實現
Ordered
;不關心順序 ? 可不實現
Ordered
;權限校驗類 Filter ? 推薦
getOrder() = -1 或更小
。
📄六、斷言與過濾器
6.1? 一句話區分:
斷言(Predicate):判斷「是否進入」某條路由。
過濾器(Filter):在進入或返回時「做點處理」。
6.2 更通俗的比喻:
把 Gateway 想象成一個大門口:
斷言 = 門口的保安:檢查條件是否滿足,比如:路徑是不是
/user/**
,IP 是不是白名單,如果不符合,就不讓進。過濾器 = 安檢員+引導員:你進門之后,它檢查你有沒有帶違禁品(請求修改),或者走的時候送你一張發票(響應修改)。
6.3 什么時候用哪個?
作用 用斷言 用過濾器 控制哪些請求能進來 ? 是的,比如判斷路徑、方法、參數等 ? 不負責判斷入口 修改請求內容(如添加請求頭) ? 做不到 ? 過濾器可以 修改響應內容(如統一返回格式) ? 做不到 ? 過濾器可以 鑒權(Token 檢查) ? 不負責 ? 通常放過濾器中 路由轉發前的數據加工 ? 不處理 ? 可以改路徑、改頭信息等
🧰 七、常見問題及排查技巧
問題 解決方式 請求不轉發 檢查 routes 中的 path 和 uri 是否正確 路徑不匹配 加上 StripPrefix 或 RewritePath 服務名不識別 使用 Nacos 并開啟服務發現 過濾器無效 全局:實現 GlobalFilter;局部:添加在路由 filters 中 請求頭 token 丟失 加日志觀察請求流轉路徑;部分服務可能會過濾頭部字段
🧠 八、總結
Spring Cloud Gateway 是基于 WebFlux 的高性能網關組件
推薦在微服務中作為 API 網關統一入口使用
配置靈活,可通過 YML 實現路徑轉發、負載均衡
支持斷言(匹配規則)和過濾器(增強功能)
可結合 Nacos、OAuth2 等組件構建更復雜的網關服務