Spring Cloud Gateway是 Spring Cloud 生态系统中的一个 API 网关服务,用于替换由Zuul开发的网关服务,基于Spring 5.0+Spring Boot 2.0+WebFlux等技术开发,提供了网关的基本功能,例如安全、监控、埋点和限流等,旨在为微服务架构提供一种简单而有效的统一 API 路由管理方式。
1.网关路由
1.1.认识网关
什么是网关?顾明思议,网关就是网 络的关 口。数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由 和转发以及数据安全的校验。

1.2 Gateway特性
Gateway具有以下主要特性:
-
动态路由:能够匹配任何请求属性上的路由
-
断言(Predicate)和过滤器(Filter):针对特定路由的灵活配置
-
集成 Hystrix 断路器:提供熔断功能
-
服务发现集成:与 Eureka、Consul 等服务发现组件无缝集成
-
请求限流:支持基于多种策略的限流
-
路径重写:支持请求路径的重写
1.3 Gateway相关术语
① 路由(Route):路由是网关的基本组件,Gateway包含多个路由,每个路由包含唯一的ID(路由编号)、目标URI(即请求最终被转发到的目的地URI)、路由断言集合和过滤器集合。
②断言(Predicate):实际上就是Java 8 Function Predicate的断言功能,即匹配条件,只有满足条件的请求才会被路由到目标URI。输入类型是 Spring Framework ServerWebExchange。其允许开发人员自行匹配来自HTTP请求的任何内容,例如HTTP头或参数。
③过滤器(Filter):作用类似于拦截加工,对于经过过滤器的请求和响应,都可以进行修改,例如Spring Framework GatewayFilter实例,可以在发送下游请求之前或之后修改请求和响应。
2. Gateway工作流程
当Gateway客户端向Gateway服务端发送请求时,请求首先被HttpWebHandlerAdapter提取组装成网关上下文,然后网关上下文会传递到DispatcherHandler中。DispatcherHandler是所有请求的分发处理器,主要负责分发请求对应的处理器,比如将请求分发到对应的RoutePredicateHandlerMapping (路由断言处理映射器)。路由断言处理映射器主要用于路由查找,以及找到路由后返回对应的FilterWebHandler。FilterWebHandler主要负责组装过滤器链并调用过滤器执行一系列过滤处理,然后把请求转到后端对应的代理服务处理,处理完毕之后将反馈信息

Spring Cloud Gateway 的核心流程:
客户端请求 → Gateway Handler Mapping → Gateway Web Handler → 过滤器链 → 代理服务
3. Spring Cloud Gateway案例
案例说明:创建两个简单的微服务模拟服务提供者和网关
3.1 父工程
创建父工程统一管理Spring Boot、Spring Cloud和Spring Cloud Alibaba,pom.xml文件代码如下所示。
XML
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hl</groupId>
<artifactId>shop</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>order-consumer</module>
<module>order-provider</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.12</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>2021.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
<spring-boot-web.version>2.7.12</spring-boot-web.version>
</properties>
<dependencyManagement>
<dependencies>
<!--spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
3.2 service-provider微服务------服务提供者
在父工程中创建service-provider微服务,整合Nacos注册中心,并创建一个"/hello"接口来模拟服务提供者。
① 修改pom.xml文件,追加Nacos服务发现组件spring-cloud-starter-alibaba-nacos-discovery,代码如下所示。
XML
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.hl</groupId>
<artifactId>shop</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>order-provider</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
② 在service-provider微服务的src/main/resources目录下创建application.yml文件,配置服务端口号为8081、微服务名为"service-provider"、Nacos注册中心地址为"localhost:8848",代码如下所示。
XML
server:
port: 8081
spring:
application:
name: service-provide
profiles:
active: dev
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
③ 按照Spring Boot规范创建项目启动类ServiceProviderApplication,在该启动类上追加@EnableDiscoveryClient注解(该注解表示向Nacos注册中心注册微服务),开启服务注册与发现功能,代码如下所示。
java
package com.hl;
import com.hl.config.OpenFeignLoggerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class ServiceProviderApplication{
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class, args);
}
}
④ 创建ProviderController类,在该类上追加@RestController注解,在该类中定义一个hello()方法,返回"hello"及传进来的实参name。
java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProviderController {
@GetMapping("/hello")
public String hello(@RequestParam String name) {
return "hello " + name + "!";
}
}
3.3 service-gateway微服务------网关
创建微服务service-gateway,并整合到Nacos注册中心。
① 修改pom.xml文件,追加Nacos服务发现组件spring-cloud-starter-alibaba-nacos-discovery及Gateway依赖spring-cloud-starter-gateway,修改后的pom.xml文件代码如下所示。
XML
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.hl</groupId>
<artifactId>shop</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>service-gateway</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--添加 Gateway依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
</project>
② 在service-gateway微服务的src/main/resources目录下创建application.yml文件,配置服务端口号为8001、微服务名为"service-gateway、Nacos注册中心地址为"localhost:8848",spring.cloud.gateway.discovery.locator.enabled为true开启Gateway服务发现,即Gateway将使用服务发现来动态路由请求,application.yml代码如下所示。
XML
server:
port: 8001
spring:
application:
name: service-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true
③ 按照Spring Boot规范创建项目启动类ServiceGatewayApplication,在该启动类上追加@EnableDiscoveryClient注解,开启服务注册与发现功能,代码如下所示。
java
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceGatewayApplication.class, args);
}
}
3.4 测试Gateway路由转发
在IDEA工具中启动两个微服务:service-provider和service-gateway。启动Nacos,访问http://localhost:8848/nacos,选择"服务管理"的"服务列表",可发现service-provider和service-gateway微服务实例,说明微服务已成功注册到了Nacos注册中心

通过Gateway端口号8001及服务名"service-provider"访问服务接口,即访问http://localhost: 8001/service-provider/hello?name=gateway,其中显示了"hello gateway"。至此基于网关实现了路由转发。

4. Gateway过滤器工厂
路由规则的定义语法如下:
java
spring:
cloud:
gateway:
routes:
- id: item
uri: lb://item-service
predicates:
- Path=/items/**,/search/**
filters:
- AddRequestHeader=X-Request-red, blue
四个属性含义如下:
-
id
:路由的唯一标示 -
predicates
:路由断言,其实就是匹配条件 -
filters
:路由过滤条件 -
uri
:路由目标地址,lb://
代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。
过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。Gateway内置丰富的过滤器,例如AddRequestHeader、AddRequestParameter、AddResponseHeader、RemoveRequestHeader、StripPrefix、RewritePath、LoadBalancerClientFilter
4.1 AddRequestHeaderGatewayFilterFacotry
顾明思议,就是添加请求头的过滤器,可以给请求添加一个请求头并传递到下游微服务。
使用的使用只需要在application.yaml中这样配置:
java
spring:
cloud:
gateway:
routes:
- id: test_route
uri: lb://test-service
predicates:
-Path=/test/**
filters:
- AddRequestHeader=key, value # 逗号之前是请求头的key,逗号之后是value
如果想要让过滤器作用于所有的路由,则可以这样配置:
java
spring:
cloud:
gateway:
default-filters: # default-filters下的过滤器可以作用于所有路由
- AddRequestHeader=key, value
routes:
- id: test_route
uri: lb://test-service
predicates:
-Path=/test/**
5. Gateway路由断言工厂
Gateway包括很多路由断言,当HTTP请求进入Gateway之后,由于实际工作中Gateway中存在多个路由,因此路由断言会根据配置的路由规则对请求进行断言匹配,若匹配成功则从相应路由转发。
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=**.somehost.org,**.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
weight | 权重处理 |
5.1 Header路由断言
Header路由断言有两个参数:Header名称(name)和正则表达式形式的值(value)。该路由断言用于匹配具有给定名称且值与正则表达式匹配的HTTP头。
java
spring:
cloud:
gateway:
default-filters: # default-filters下的过滤器可以作用于所有路由
- AddRequestHeader=key, value
routes:
- id: test_route
uri: lb://test-service
predicates:
- Header=X-Request-Id, \d+
6. 动态路由
路由规则是网关的核心内容,配置在应用的属性配置文件中,服务启动的时候将路由规 则加载到内存中,这属于静态路由方式。网关的路由配置全部是在项目启动时由org.springframework.cloud.gateway.route.CompositeRouteDefinitionLocator
在项目启动的时候加载,并且一经加载就会缓存到内存中的路由表内(一个Map),不会改变。也不会监听路由变更,所以我们无法利用配置热更新来实现路由更新。
可采用 Nacos 实现动态路由,把路由更新规则保存在分布式配置 中心 Nacos 中,通过 Nacos 的监听机制,动态更新每个实例的路由规则。因此,我们必须监听Nacos的配置变更 ,然后手动把最新的路由更新到路由表中。

6.1 监听Nacos配置变更
在Nacos官网中给出了手动监听Nacos配置变更的SDK:https://nacos.io/zh-cn/docs/sdk.html
监听配置:
如果希望 Nacos 推送配置变更,可以使用 Nacos 动态监听配置接口来实现。
public void addListener(String dataId, String group, Listener listener)
请求参数说明:
参数名 | 参数类型 | 描述 |
---|---|---|
dataId | string | 配置 ID,保证全局唯一性,只允许英文字符和 4 种特殊字符("."、":"、"-"、"_")。不超过 256 字节。 |
group | string | 配置分组,一般是默认的DEFAULT_GROUP。 |
listener | Listener | 监听器,配置变更进入监听器的回调函数。 |
示例代码:
java
String serverAddr = "{serverAddr}";
String dataId = "{dataId}";
String group = "{group}";
// 1.创建ConfigService,连接Nacos
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
ConfigService configService = NacosFactory.createConfigService(properties);
// 2.读取配置
String content = configService.getConfig(dataId, group, 5000);
// 3.添加配置监听器
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
// 配置变更的通知处理
System.out.println("recieve1:" + configInfo);
}
@Override
public Executor getExecutor() {
return null;
}
});
这里核心的步骤有2步:
-
创建ConfigService,目的是连接到Nacos
-
添加配置监听器,编写配置变更的通知处理逻辑
第一步:
由于我们采用spring-cloud-starter-alibaba-nacos-config
自动装配,因此ConfigService
已经在com.alibaba.cloud.nacos.NacosConfigAutoConfiguration
中自动创建好了:

NacosConfigManager中是负责管理Nacos的ConfigService的,具体代码如下:

因此,只要我们拿到NacosConfigManager
就等于拿到了ConfigService
第二步:
编写监听器。虽然官方提供的SDK是ConfigService中的addListener,不过项目第一次启动时不仅仅需要添加监听器,也需要读取配置,因此建议使用的API是这个:

String getConfigAndSignListener(
String dataId, // 配置文件id
String group, // 配置组,走默认
long timeoutMs, // 读取配置的超时时间
Listener listener // 监听器
) throws NacosException;
既可以配置监听器,并且会根据dataId和group读取配置并返回。我们就可以在项目启动时先更新一次路由,后续随着配置变更通知到监听器,完成路由更新。
6.2 更新路由
Gateway 提供了修改路由的接口 RouteDefinitionWriter,只有通过这个接口才能修改动态路由。
java
package org.springframework.cloud.gateway.route;
import reactor.core.publisher.Mono;
/**
* @author Spencer Gibb
*/
public interface RouteDefinitionWriter {
/**
* 更新路由到路由表,如果路由id重复,则会覆盖旧的路由
*/
Mono<Void> save(Mono<RouteDefinition> route);
/**
* 根据路由id删除某个路由
*/
Mono<Void> delete(Mono<String> routeId);
}
这里更新的路由,也就是RouteDefinition,包含下列常见字段:
-
id:路由id
-
predicates:路由匹配规则
-
filters:路由过滤器
-
uri:路由目的地
将来我们保存到Nacos的配置也要符合这个对象结构,将来我们以JSON来保存,格式如下:
java
{
"id": "item",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
}],
"filters": [],
"uri": "lb://item-service"
}
以上JSON配置就等同于:
java
spring:
cloud:
gateway:
routes:
- id: item
uri: lb://item-service
predicates:
- Path=/items/**,/search/**
6.3 .实现动态路由
首先引入依赖:
java
<!--统一配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--加载bootstrap-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
然后在Nacos控制台添加路由,路由文件名为gateway-routes.json
,类型为json
:
java
[
{
"id": "item",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
}],
"filters": [],
"uri": "lb://item-service"
}
]
然后在网关gateway
的resources
目录创建bootstrap.yaml
文件,内容如下:
java
server:
port: 8080 # 端口
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost
config:
file-extension: yaml
shared-configs:
- dataId: gateway-routes.json # 动态路由配置
然后,在gateway
中定义配置监听器:
java
package com.hmall.gateway.route;
import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.hmall.common.utils.CollUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {
private final RouteDefinitionWriter writer;
private final NacosConfigManager nacosConfigManager;
// 路由配置文件的id和分组
private final String dataId = "gateway-routes.json";
private final String group = "DEFAULT_GROUP";
// 保存更新过的路由id
private final Set<String> routeIds = new HashSet<>();
@PostConstruct
public void initRouteConfigListener() throws NacosException {
// 1.注册监听器并首次拉取配置
String configInfo = nacosConfigManager.getConfigService()
.getConfigAndSignListener(dataId, group, 5000, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
updateConfigInfo(configInfo);
}
});
// 2.首次启动时,更新一次配置
updateConfigInfo(configInfo);
}
private void updateConfigInfo(String configInfo) {
log.debug("监听到路由配置变更,{}", configInfo);
// 1.反序列化
List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
// 2.更新前先清空旧路由
// 2.1.清除旧路由
for (String routeId : routeIds) {
writer.delete(Mono.just(routeId)).subscribe();
}
routeIds.clear();
// 2.2.判断是否有新的路由要更新
if (CollUtils.isEmpty(routeDefinitions)) {
// 无新路由配置,直接结束
return;
}
// 3.更新路由
routeDefinitions.forEach(routeDefinition -> {
// 3.1.更新路由
writer.save(Mono.just(routeDefinition)).subscribe();
// 3.2.记录路由id,方便将来删除
routeIds.add(routeDefinition.getId());
});
}
}