Spring?Cloud?Gateway編碼實(shí)現(xiàn)任意地址跳轉(zhuǎn)
本篇概覽 作為《Spring Cloud Gateway實(shí)戰(zhàn)》系列的第十四篇,本文會(huì)繼續(xù)發(fā)掘Spring Cloud Gateway的潛力,通過編碼體驗(yàn)操控網(wǎng)關(guān)的樂趣,開發(fā)出一個(gè)實(shí)用的功能:讓Spring Cloud Gateway應(yīng)用在收到請(qǐng)求后,可以按照業(yè)務(wù)的需要跳轉(zhuǎn)到任意的地址去
一般路由規(guī)則
先來看一個(gè)普通的路由規(guī)則,如下所示,意思是將所有/hello/**的請(qǐng)求轉(zhuǎn)發(fā)到http://127.0.0.1:8082這個(gè)地址去:
spring:
application:
name: hello-gateway
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- Path=/hello/**上述規(guī)則的功能如下圖所示,假設(shè)這就是生產(chǎn)環(huán)境的樣子,192.168.50.99:8082是提供服務(wù)的后臺(tái)應(yīng)用:

特殊規(guī)則
以上是常規(guī)情況,但也有些特殊情況,要求SpringCloud Gateway把瀏覽器的請(qǐng)求轉(zhuǎn)發(fā)到不同的服務(wù)上去
如下圖所示,在之前的環(huán)境中增加了另一個(gè)服務(wù)(即藍(lán)色塊),假設(shè)藍(lán)色服務(wù)代表測(cè)試環(huán)境

瀏覽器發(fā)起的/hello/str請(qǐng)求中,如果header中帶有tag-test-user,并且值等于true,此時(shí)要求SpringCloud Gateway把這個(gè)請(qǐng)求轉(zhuǎn)發(fā)到測(cè)試環(huán)境
如果瀏覽器的請(qǐng)求header中沒有tag-test-user,SpringCloud Gateway需要像以前那樣繼續(xù)轉(zhuǎn)發(fā)到192.168.50.99:8082
很明顯,上述需求難以通過配置來實(shí)現(xiàn),因?yàn)檗D(zhuǎn)發(fā)的地址和轉(zhuǎn)發(fā)邏輯都是圍繞業(yè)務(wù)邏輯來定制的,這也就是本篇的目標(biāo):對(duì)同一個(gè)請(qǐng)求path,可以通過編碼轉(zhuǎn)發(fā)到不同的地方去
實(shí)現(xiàn)上述功能的具體做法是:自定義過濾器
設(shè)計(jì)
編碼之前先設(shè)計(jì),把關(guān)鍵點(diǎn)想清楚再動(dòng)手
今天咱們要開發(fā)一個(gè)SpringCloud Gateway應(yīng)用,里面新增一個(gè)自定義過濾器
實(shí)現(xiàn)這個(gè)功能需要三個(gè)知識(shí)點(diǎn)作為基礎(chǔ),也就是說,您會(huì)通過本篇實(shí)戰(zhàn)掌握以下知識(shí)點(diǎn):
- 自定義過濾器
- 自定義過濾器的配置參數(shù)和bean的映射
- 編碼構(gòu)造Route實(shí)例
用思維導(dǎo)圖將具體工作內(nèi)容展開,如下圖所示,咱們就按部就班的實(shí)現(xiàn)吧:

源碼下載
本篇實(shí)戰(zhàn)中的完整源碼可在GitHub下載到,地址和鏈接信息如下表所示(https://github.com/zq2599/blog_demos):
| 名稱 | 鏈接 | 備注 |
|---|---|---|
| 項(xiàng)目主頁 | https://github.com/zq2599/blog_demos | 該項(xiàng)目在GitHub上的主頁 |
| git倉庫地址(https) | https://github.com/zq2599/blog_demos.git | 該項(xiàng)目源碼的倉庫地址,https協(xié)議 |
| git倉庫地址(ssh) | git@github.com:zq2599/blog_demos.git | 該項(xiàng)目源碼的倉庫地址,ssh協(xié)議 |
這個(gè)git項(xiàng)目中有多個(gè)文件夾,本篇的源碼在spring-cloud-tutorials文件夾下,如下圖紅框所示:

- spring-cloud-tutorials內(nèi)部有多個(gè)子項(xiàng)目,本篇的源碼在gateway-dynamic-route文件夾下,如下圖紅框所示:

編碼
新建名為gateway-dynamic-route的maven工程,其pom.xml內(nèi)容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tutorials</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway-dynamic-route</artifactId>
<dependencies>
<dependency>
<groupId>com.bolingcavalry</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 如果父工程不是springboot,就要用以下方式使用插件,才能生成正常的jar -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.bolingcavalry.gateway.GatewayDynamicRouteApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
啟動(dòng)類是普通的SpringBoot啟動(dòng)類:
package com.bolingcavalry.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayDynamicRouteApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayDynamicRouteApplication.class,args);
}
}
接下來是本篇的核心,自定義過濾器類,代碼中已經(jīng)添加了詳細(xì)的注釋,有幾處要注意的地方稍后會(huì)提到:
package com.bolingcavalry.gateway.filter;
import lombok.Data;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
@Component
@Slf4j
public class BizLogicRouteGatewayFilterFactory extends AbstractGatewayFilterFactory<BizLogicRouteGatewayFilterFactory.BizLogicRouteConfig> {
private static final String TAG_TEST_USER = "tag-test-user";
public BizLogicRouteGatewayFilterFactory() {
super(BizLogicRouteConfig.class);
}
@Override
public GatewayFilter apply(BizLogicRouteConfig config) {
return (exchange, chain) -> {
// 本次的請(qǐng)求對(duì)象
ServerHttpRequest request = exchange.getRequest();
// 調(diào)用方請(qǐng)求時(shí)的path
String rawPath = request.getURI().getRawPath();
log.info("rawPath [{}]", rawPath);
// 請(qǐng)求頭
HttpHeaders headers = request.getHeaders();
// 請(qǐng)求方法
HttpMethod httpMethod = request.getMethod();
// 請(qǐng)求參數(shù)
MultiValueMap<String, String> queryParams = request.getQueryParams();
// 這就是定制的業(yè)務(wù)邏輯,isTestUser等于ture代表當(dāng)前請(qǐng)求來自測(cè)試用戶,需要被轉(zhuǎn)發(fā)到測(cè)試環(huán)境
boolean isTestUser = false;
// 如果header中有tag-test-user這個(gè)key,并且值等于true(不區(qū)分大小寫),
// 就認(rèn)為當(dāng)前請(qǐng)求是測(cè)試用戶發(fā)來的
if (headers.containsKey(TAG_TEST_USER)) {
String tageTestUser = headers.get(TAG_TEST_USER).get(0);
if ("true".equalsIgnoreCase(tageTestUser)) {
isTestUser = true;
}
}
URI uri;
if (isTestUser) {
log.info("這是測(cè)試用戶的請(qǐng)求");
// 從配置文件中得到測(cè)試環(huán)境的uri
uri = UriComponentsBuilder.fromHttpUrl(config.getTestEnvUri() + rawPath).queryParams(queryParams).build().toUri();
} else {
log.info("這是普通用戶的請(qǐng)求");
// 從配置文件中得到正式環(huán)境的uri
uri = UriComponentsBuilder.fromHttpUrl(config.getProdEnvUri() + rawPath).queryParams(queryParams).build().toUri();
}
// 生成新的Request對(duì)象,該對(duì)象放棄了常規(guī)路由配置中的spring.cloud.gateway.routes.uri字段
ServerHttpRequest serverHttpRequest = request.mutate().uri(uri).method(httpMethod).headers(httpHeaders -> httpHeaders = httpHeaders).build();
// 取出當(dāng)前的route對(duì)象
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
//從新設(shè)置Route地址
Route newRoute =
Route.async().asyncPredicate(route.getPredicate()).filters(route.getFilters()).id(route.getId())
.order(route.getOrder()).uri(uri).build();
// 放回exchange中
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR,newRoute);
// 鏈?zhǔn)教幚?,交給下一個(gè)過濾器
return chain.filter(exchange.mutate().request(serverHttpRequest).build());
};
}
/**
* 這是過濾器的配置類,配置信息會(huì)保存在此處
*/
@Data
@ToString
public static class BizLogicRouteConfig {
// 生產(chǎn)環(huán)境的服務(wù)地址
private String prodEnvUri;
// 測(cè)試環(huán)境的服務(wù)地址
private String testEnvUri;
}
}
上述代碼中要注意的地方如下: BizLogicRouteConfig是過濾器的配置類,可以在使用過濾器時(shí)在配置文件中配置prodEnvUri和testEnvUri的值,在代碼中可以通過這兩個(gè)字段取得配置值
過濾器的工廠類名為BizLogicRouteGatewayFilterFactory,按照規(guī)則,過濾器的名字是BizLogicRoute 在apply方法中,重新創(chuàng)建ServerHttpRequest和Route對(duì)象,它們的參數(shù)可以按照業(yè)務(wù)需求隨意設(shè)置,然后再將這兩個(gè)對(duì)象設(shè)置給SpringCloud gateway的處理鏈中,接下來,處理鏈上的其他過濾拿到的就是新的ServerHttpRequest和Route對(duì)象了
配置
假設(shè)生產(chǎn)環(huán)境地址是http://127.0.0.1:8082,測(cè)試環(huán)境地址是http://127.0.0.1:8087,整個(gè)SpringCloud Gateway應(yīng)用的配置文件如下,可見使用了剛剛創(chuàng)建的過濾器,并且為此過濾器配置了兩個(gè)參數(shù):
server:
#服務(wù)端口
port: 8086
spring:
application:
name: gateway-dynamic-route
cloud:
gateway:
routes:
- id: path_route
uri: http://0.0.0.0:8082
predicates:
- Path=/hello/**
filters:
- name: BizLogicRoute
args:
prodEnvUri: http://127.0.0.1:8082
testEnvUri: http://127.0.0.1:8087至此,編碼完成了,啟動(dòng)這個(gè)服務(wù)
開發(fā)和啟動(dòng)后臺(tái)服務(wù),模擬生產(chǎn)和測(cè)試環(huán)境
接下來開始驗(yàn)證功能是否生效,咱們要準(zhǔn)備兩個(gè)后臺(tái)服務(wù):
模擬生產(chǎn)環(huán)境的后臺(tái)服務(wù)是provider-hello,監(jiān)聽端口是8082,其/hello/str接口的返回值是Hello World, 2021-12-12 10:53:09
模擬測(cè)試環(huán)境的后臺(tái)服務(wù)是provider-for-test-user,監(jiān)聽端口是8087,其/hello/str接口的返回值是Hello World, 2021-12-12 10:57:11 (from test enviroment)(和生產(chǎn)環(huán)境相比,返回內(nèi)容多了個(gè)(from test enviroment)),對(duì)應(yīng)Controller參考如下:
package com.bolingcavalry.provider.controller;
import com.bolingcavalry.common.Constants;
import org.springframework.web.bind.annotation.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
@RestController
@RequestMapping("/hello")
public class Hello {
private String dateStr(){
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
}
/**
* 返回字符串類型
* @return
*/
@GetMapping("/str")
public String helloStr() {
return Constants.HELLO_PREFIX + ", " + dateStr() + " (from test enviroment)";
}
}
以上兩個(gè)服務(wù),對(duì)應(yīng)的代碼都在我的Github倉庫中,如下圖紅框所示:

啟動(dòng)gateway-dynamic-route、provider-hello、provider-for-test-user服務(wù)
此時(shí),SpringCloud gateway應(yīng)用和兩個(gè)后臺(tái)服務(wù)都啟動(dòng)完成,情況如下圖,接下來驗(yàn)證剛才開發(fā)的過濾器能不能像預(yù)期那樣轉(zhuǎn)發(fā):

驗(yàn)證
用postman工具向gateway-dynamic-route應(yīng)用發(fā)起一次請(qǐng)求,返回值如下圖紅框所示,證明這是provider-hello的響應(yīng),看來咱們的請(qǐng)求已經(jīng)正常到達(dá):

再發(fā)送一次請(qǐng)求,如下圖,這次在header中加入鍵值對(duì),得到的結(jié)果是provider-for-test-user的響應(yīng)

至此,過濾器的開發(fā)和驗(yàn)證已經(jīng)完成,通過編碼,可以把外部請(qǐng)求轉(zhuǎn)發(fā)到任何咱們需要的地址去,并且支持參數(shù)配置,這個(gè)過濾器還有一定的可配置下,減少了硬編碼的比率,如果您正在琢磨如何深度操控SpringCloud Gateway,希望本文能給您一些參考!
到此這篇關(guān)于Spring Cloud Gateway編碼實(shí)現(xiàn)任意地址跳轉(zhuǎn)的文章就介紹到這了,更多相關(guān)Spring Cloud Gateway實(shí)現(xiàn)任意地址跳轉(zhuǎn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解Spring Cloud Gateway 限流操作
- spring cloud gateway使用 uri: lb://方式配置時(shí),服務(wù)名的特殊要求
- 基于Nacos實(shí)現(xiàn)Spring Cloud Gateway實(shí)現(xiàn)動(dòng)態(tài)路由的方法
- 解決spring cloud gateway 獲取body內(nèi)容并修改的問題
- springcloud gateway如何實(shí)現(xiàn)路由和負(fù)載均衡
- springcloud gateway聚合swagger2的方法示例
- 詳解SpringCloud Gateway之過濾器GatewayFilter
- Spring Cloud Gateway全局異常處理的方法詳解
- spring cloud gateway整合sentinel實(shí)現(xiàn)網(wǎng)關(guān)限流
相關(guān)文章
JAVA中通過自定義注解進(jìn)行數(shù)據(jù)驗(yàn)證的方法
java 自定義注解驗(yàn)證可自己添加所需要的注解,下面這篇文章主要給大家介紹了關(guān)于JAVA中通過自定義注解進(jìn)行數(shù)據(jù)驗(yàn)證的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
spring security集成cas實(shí)現(xiàn)單點(diǎn)登錄過程
這篇文章主要介紹了spring security集成cas實(shí)現(xiàn)單點(diǎn)登錄過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
SpringBoot @JsonDeserialize自定義Json序列化方式
這篇文章主要介紹了SpringBoot @JsonDeserialize自定義Json序列化方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10

