3分钟彻底吃透 Spring Cloud Gateway 服务网关技术!
一、背景介绍
在之前的 Spring Cloud 系列文章中,我们简单的介绍了 Zuul 的工作流程以及作为服务网关的使用方式,相信大家对它已经有了初步的印象。
Zuul 作为 Spring Cloud 第一代技术生态中的服务网关核心组件,性能还是挺不错的。不过随着 Netflix 逐步停止对 Zuul 进行技术更新迭代,Spring 团队便推出了自研的 Spring Cloud Gateway,以便取代 Zuul。
Spring Cloud Gateway 是基于 Spring 5、Spring Boot 2 和 Project Reactor 等技术构建的 API 网关,旨在为微服务架构提供一种简单且有高效的 API 路由管理功能。它是 Spring Cloud 生态系统中很重要的一部分,主要用于处理请求的路由、负载均衡、安全、监控等任务。
简单的说,你可以把它看成 Zuul 的替代品。与此同时,与 Zuul 相比,Spring Cloud Gateway 性能更加优秀,功能更加强大。
今天通过这篇文章,我们一起来了解一下 Spring Cloud Gateway 的工作流程及使用方式。
二、工作流程
在 Spring Cloud Gateway 中,有三个核心的组件,分别如下:
- Route:路由,网关中最基本的组件之一。它由一个 ID,一个目标 URI,一组谓语和一组过滤器定义。如果谓语为真,则进行路由匹配
- Predicate:谓语,作为路由的匹配条件。这是一个 Java 8 的 Predicate,输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,包括 headers 或参数。
- Filter:过滤器,支持对请求参数和返回结果进行拦截修改。
整体服务的请求流程,可以用如下图来概括。

当客户端向 Spring Cloud Gateway 发出请求时,大体会经过如下几个步骤:
- 第一步:将请求与 Predicate 进行匹配,获得到对应的 Route。如果匹配成功,请求将被发送到 Gateway Web Handler 处理程序。
- 第二步:Handler 将请求放入 Filter 过滤器链中,执行前置(prev)处理逻辑。例如,修改请求头信息等。
- 第三步:接着请求被 Proxy Filter 转发至目标 URI,并最终获得响应。这一步执行最终要转发的目标服务
- 第四步:将请求响应结果丢入 Filter 过滤器链,执行后置(post)处理逻辑。例如,修改响应头信息等。
- 第五步:Gateway 将最终响应结果返回给客户端。
整体流程和 SpringMVC 的 DispatcherServlet 差不多,只是说 SpringMVC 最终转发到 JVM 进程内的指定方法,而 Gateway 最终转发到远程的目标 URI。
在微服务架构中引入 Spring Cloud Gateway 后,整体的工作流程可以用如下图来简要概括。

三、快速入门
使用 Spring Cloud Gateway 来构建服务网关非常的简单,通过以下三步骤即可完成。
3.1、创建服务网关
首先创建一个 Spring Boot 工程,命名为gateway-server
,并在pom.xml
中引入 Gateway 依赖包,示例如下:
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring-boot.version>2.2.5.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
<dependencies>
<!-- 引入 Spring Cloud Gateway 网关组件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- 引入 springBoot 版本号 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 引入 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>
</dependencies>
</dependencyManagement>
3.2、创建服务启动类
然后,创建一个服务全局启动类。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
3.3、配置服务路由规则
最后,创建application.yaml
文件并配置相关路由规则,示例如下:
server:
port: 8080
spring:
cloud:
# Spring Cloud Gateway 配置项,对应 GatewayProperties 类
gateway:
# 路由配置项,支持多个匹配,对应 RouteDefinition 数组
routes:
- id: baidu # 路由的编号
uri: https://www.baidu.com # 路由到的目标地址
predicates: # 谓语,作为路由的匹配条件
- Path=/baidu
filters: # 过滤器,对请求进行拦截,移除前缀路径
- StripPrefix=1
- id: myRoute # 路由的编号
uri: http://127.0.0.1:9001 # 路由到的目标地址
predicates: # 谓语,作为路由的匹配条件
- Path=/myRoute/**
filters: # 过滤器,对请求进行拦截,移除前缀路径
- StripPrefix=1
关键参数作用解读:
routes
:用于定义路由配置项,支持多个匹配id
:路由的唯一标识,不能重复uri
:路由指向的目标 URI,即请求最终被转发的目的地。predicates
:路由中的匹配条件。Gateway 内置了多种 Predicate 的实现,提供了多种请求的匹配条件,上文中配置的是基于请求Path
路由filters
:过滤器,可以实现对请求进行拦截。Gateway 内置了多种 Filter 的实现,比如说限流、熔断等等。
在上文我们配置了 2 个路由规则,分别是/baidu
和/myRoute/**
,当请求路径中含有此关键字时,分别会路由到不同的目标服务。
同时,在每个路由规则下还配置了StripPrefix
过滤器,它是一种会将请求的 Path
去除掉前缀的过滤器,比如请求http://127.0.0.1:8080/myRoute/hello
时,会转成https://127.0.0.1:9001/hello
,如果没有配置就会转发到https://127.0.0.1:9001/myRoute/hello
。
3.4、构建业务微服务
为了便于测试服务的路由效果,在上文中我们配置了一个转发到127.0.0.1:9001
的路由规则,它其实是我本地创建的一个 Spring Boot 服务。
工程的创建过程就不在重复介绍了,里面中的示例接口如下:
@RestController
public class HelloController {
@GetMapping("/hello")
public String index() {
return "hello world!";
}
}
3.5、服务测试
最后,将服务网关、业务微服务启动起来,在浏览器中访问上文配置的路径,看看会返回什么。
访问http://127.0.0.1:8080/baidu
,自动转发到https://www.baidu.com
,结果如下图:

再次访问http://127.0.0.1:8080/myRoute/hello
,自动转发到https://127.0.0.1:9001/hello
,结果如下图:

可以清晰的看到,配置的路由规则都被成功转发到目标服务。
四、路由规则介绍
Spring Cloud Gateway 的路由规则其实非常强大,在上文中我们只是简单的介绍了其中一个基于Path
方式的路由匹配条件。
实际上,Spring Cloud Gataway 帮我们内置了很多 Predicates 功能,可以将请求匹配到对应的 Route 上。
下面是一张网友总结的 Spring Cloud 内置的几种 Predicate 实现。

内置的路由匹配规则基本上可以满足我们绝大多数的需求,并且多个 Route Predicate 还可以组合实现,因此我们只需要掌握它的使用技巧即可。
不过一般情况下,我们主要使用Path
和Host
比较多一些,甚至只使用Path
。所以具体到每个 Predicate 的使用,如果有相应的需求,大家可以直接参照如下官方文档案例进行配置即可。
https://springdoc.cn/spring-cloud-gateway/
五、自定义过滤器
在上文中我们提到了过滤器的作用,它可以对请求参数和返回结果进行拦截修改。
在实际的项目开发中,过滤器的这一特性应用也非常广泛,比如利用它来实现接口认证,接口限流,改写接口响应结果等。
在 Spring Cloud Gateway 中,过滤器分为 GlobalFilter 和 GatewayFilter 两种,二者区别如下:
- GlobalFilter : 全局过滤器,不需要在配置文件中配置,系统初始化时加载,作用在所有的路由规则上。
- GatewayFilter : 局部过滤器,需要通过
spring.cloud.routes.filters
参数配置在具体路由上,只作用在配置的路由规则上。
如何自定义 Filter 实现对请求和响应数据的拦截修改呢?下面我们一起来看看!
5.1、自定义 GlobalFilter
要自定义全局过滤器,非常简单,只需实现Spring Cloud Gateway GlobalFilter
接口,并将其作为Bean
添加到上下文中即可。
例如,定义一个 “Pre” 类型的过滤器,示例如下:
@Component
public class PreGlobalFilter implements GlobalFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(PreGlobalFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 在这里添加你的前置逻辑,比如修改请求头、参数等
LOGGER.info("Global Pre Filter executed");
return chain.filter(exchange);
}
}
定义一个 “Post” 类型的过滤器,示例如下:
@Component
public class PostGlobalFilter implements GlobalFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(PostGlobalFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
// 在这里添加你的后置逻辑,如响应后处理
LOGGER.info("Global Post Filter executed");
})
);
}
}
实际上,Filter 并不区分pre
和post
类型,两者可以在一个过滤器中完成,示例如下:
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 在这里添加你的前置逻辑,比如修改请求头、参数等
LOGGER.info("Pre Filter executed");
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
// 在这里添加你的后置逻辑,如响应后处理
LOGGER.info("Post Filter executed");
})
);
}
启动服务,访问服务网关接口,可以看到自定义的全局过滤器日志输出情况。

5.2、自定义 GatewayFilter
某些时候,我们希望为某个路由规则自定义过滤器,比如某个服务路由新增接口安全认证,此时我们可以借助AbstractGatewayFilterFactory
抽象类来实现局部过滤器效果。
以 token 鉴权为例,实现过程如下!
5.2.1、编写过滤器实现类
首先,创建一个TokenGatewayFilterFactory
类并继承AbstractGatewayFilterFactory
类。
示例如下:
@Component
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.Config> {
private static final Logger LOGGER = LoggerFactory.getLogger(TokenGatewayFilterFactory.class);
public TokenGatewayFilterFactory() {
super(TokenGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(Config config) {
// 模拟某个token与用户的关系
Map<String, Integer> tokenMap = new HashMap<>();
tokenMap.put("hahaha", 1);
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
LOGGER.info("TokenGatewayFilterFactory pre Filter executed");
// 获取请求参数
ServerHttpRequest request = exchange.getRequest();
// 获取请求路径
String requestPath = request.getPath().toString();
// 针对一些特殊路径,跳过认证
if("/dc".equals(requestPath)){
return chain.filter(exchange);
}
// 获取返回参数
ServerHttpResponse response = exchange.getResponse();
// 从请求头部中获取token值,并进行认证
String token = request.getHeaders().getFirst(config.getTokenHeaderName());
Integer userId = tokenMap.get(token);
if(userId == null){
// 响应 401 状态码
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 响应提示
DataBuffer buffer = response.bufferFactory().wrap("当前 token 非法,认证不通过!".getBytes());
return response.writeWith(Flux.just(buffer));
}
// 将用户ID存入到请求头部中
ServerHttpRequest newRequest = request.mutate().header(config.getUserIdHeaderName(), String.valueOf(userId)).build();
return chain.filter(exchange.mutate().request(newRequest).build());
}
};
}
public static class Config {
private String tokenHeaderName;
private String userIdHeaderName;
public String getTokenHeaderName() {
return tokenHeaderName;
}
public void setTokenHeaderName(String tokenHeaderName) {
this.tokenHeaderName = tokenHeaderName;
}
public String getUserIdHeaderName() {
return userIdHeaderName;
}
public void setUserIdHeaderName(String userIdHeaderName) {
this.userIdHeaderName = userIdHeaderName;
}
}
}
值得注意的是,类名最好统一以GatewayFilterFactory
结尾,因为这种类型的过滤器规则就是这么约定的。
其次,在TokenGatewayFilterFactory
类中定义了一个Config
配置类,用于传递需要的参数。
同时,在TokenGatewayFilterFactory
构造方法中,需要将Config
类传递给父构造方法,保证能够正确创建Config
对象。
5.2.2、注册 GatewayFilter
过滤器实现类编写完成以后,还需要将其注册到过滤器链中。
最后,修改配置文件并添加自定义的 Filter 配置。

示例代码如下:
spring:
cloud:
# Spring Cloud Gateway 配置项,对应 GatewayProperties 类
gateway:
# 路由配置项,支持多个匹配,对应 RouteDefinition 数组
routes:
- id: myRoute # 路由的编号
uri: http://127.0.0.1:9001 # 路由到的目标地址
predicates: # 谓语,作为路由的匹配条件
- Path=/myRoute/**
filters: # 过滤器,对请求进行拦截,移除前缀路径
- StripPrefix=1
- name: Token
args:
tokenHeaderName: token
userIdHeaderName: userId
有个地方需要特别注意,就是filters.name
属性的值,只需要填写过滤器名称,忽略GatewayFilterFactory
后缀。
如果想将这个过滤器适用于所有的路由规则,可以通过spring.cloud.gateway.default-filters
配置项实现,它是 Gateway 默认过滤器,对所有路由都生效,示例代码如下:
spring:
cloud:
# Spring Cloud Gateway 配置项,对应 GatewayProperties 类
gateway:
# 路由配置项,支持多个匹配,对应 RouteDefinition 数组
routes:
- id: myRoute
uri: http://127.0.0.1:9001
predicates:
- Path=/myRoute/**
filters: # 过滤器,对请求进行拦截,移除前缀路径
- StripPrefix=1
default-filters: # 默认过滤器,所有路由都生效
- name: Token
args:
tokenHeaderName: token
userIdHeaderName: userId
5.2.3、服务测试
启动服务,访问服务网关接口,看看效果如何。
当请求头部中没有token
时,返回结果如下图。

当请求头部中带有有效token
时,返回结果如下图。

目标服务/hello
接口请求头部输出结果如下图。

可以清晰的看到,与预期结果一致!
六、小结
最后总结一下,Spring Cloud Gateway 是一个功能强大且灵活的 API 网关,适用于现代微服务架构,其最初主要定位是取代 Netflix Zuul,成为 Spring Cloud 生态系统的新一代网关,目前看下来非常成功。
下篇文章将会继续介绍 Spring Cloud Gateway 整合服务注册中心和配置中心,实现服务的自动路由转发管理。
七、参考
1.https://cloud.spring.io/spring-cloud-gateway/reference/html/
2.https://www.iocoder.cn/Spring-Cloud/Spring-Cloud-Gateway/?self
3.http://www.ityouknow.com/springcloud/2018/12/12/spring-cloud-gateway-start.html
作者:潘志的技术笔记
出处:https://pzblog.cn/
版权归作者所有,转载请注明出处
