介绍
Spring Cloud Gateway 是 Spring Cloud 生态系统中的一个基于 Spring WebFlux 的 API 网关解决方案,旨在为微服务架构提供统一的入口点,支持路由、过滤、负载均衡等功能。它通过非阻塞的响应式编程模型,提供了高效、灵活的 API 网关能力。
核心功能
- 动态路由: 支持基于路径、Header、Query参数等规则的动态路由。
- 过滤器链: 通过全局过滤器 (Global Filter) 和局部过滤器 (Gateway Filter) 实现请求增强与响应修改。
- 负载均衡: 集成Spring Cloud LoadBalancer,支持多实例流量分发。
- 限流与熔断: 内置限流功能,支持 Redis 令牌桶算法,可结合 Resilience4j 实现熔断。
- 安全与监控: 支持JWT、OAuth2等认证机制,保护后端服务。
三大核心
1. 路由 (Route)
路由是网关的核心,用于定义请求的转发规则。他由ID、目标URL、一系列的断言和过滤器组成,如果断言为true则路由匹配成功。一个路由包含以下属性:
- ID: 路由的唯一标识。
- URI: 目标服务的地址,支持 http、lb(负载均衡)等协议。
- 断言: 定义路由匹配条件,如路径、Header、方法等。
- 过滤器: 对请求或响应进行处理的逻辑。
2. 断言 (Predicate)
断言用于判断请求是否符合路由规则。常见的断言包括:
- Path: 路径匹配。
- Method: HTTP 方法匹配。
- Header: 请求头匹配。
- Query: 请求参数匹配。
- After、Before、Between: 时间范围匹配。
3. 过滤 (Filter)
过滤器用于在请求转发前或响应返回后执行特定逻辑。常见的过滤器包括:
- 全局过滤器: 对所有路由生效,如日志记录、认证校验。
- 局部过滤器: 仅对特定路由生效,如路径重写、请求限流。
Route以服务名动态获取URL
网关服务 cloud-gateway9527 引入依赖
<!-- gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 服务注册发现consul discovery,网关也要注册进服务注册中心统一管控 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 指标监控健康检查的actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
yml配置
server:
port: 9527
spring:
application:
name: cloud-gateway
# Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
订单服务 cloud-feign-order9002:OrderGatewayController
@RestController
public class OrderGatewayController {
@Resource
private PayFeignApi payFeignApi;
@GetMapping("/feign/pay/gateway/get/{id}")
public Result getById(@PathVariable("id") Integer id) {
return payFeignApi.getById(id);
}
}
Feign接口 cloud-common-api:PayFeignApi
@FeignClient("cloud-gateway")
public interface PayFeignApi {
@GetMapping("/pay/gateway/get/{id}")
public Result getById(@PathVariable("id") Integer id);
}
支付服务 cloud-payment8001:PayGatewayController
@RestController
public class PayGatewayController {
@Resource
private PayService payService;
@GetMapping(value = "/pay/gateway/get/{id}")
public Result<Pay> getById(@PathVariable("id") Integer id) {
Pay pay = payService.getById(id);
return Result.success(pay);
}
}
测试结果
启动支付服务8001,启动订单服务9002,访问 http://localhost:9002/feign/pay/gateway/get/1 返回异常。再启动网关服务9527,访问 http://localhost:9002/feign/pay/gateway/get/1 返回成功。
Predicate常用断言
Path Route Predicate
spring:
cloud:
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
测试结果
访问 http://localhost:9527/pay/gateway/get/1 返回成功。
After Route Predicate
# 获取当前时间串
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
spring:
cloud:
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
- After=2025-04-19T15:51:15.516781+08:00[Asia/Shanghai] # 在某个时间之后可以访问
测试结果
2025-04-19 15:51:15 之前访问 http://localhost:9527/pay/gateway/get/1 返回失败。
2025-04-19 15:51:15 之后访问 http://localhost:9527/pay/gateway/get/1 返回成功。
Before Route Predicate
spring:
cloud:
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
- Before=2025-04-19T15:51:15.516781+08:00[Asia/Shanghai] # 在某个时间之前可以访问
测试结果
2025-04-19 15:51:15 之前访问 http://localhost:9527/pay/gateway/get/1 返回成功。
2025-04-19 15:51:15 之后访问 http://localhost:9527/pay/gateway/get/1 返回失败。
Between Route Predicate
spring:
cloud:
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
- Between=2025-04-19T16:11:15.516781+08:00[Asia/Shanghai],2025-04-19T16:12:15.516781+08:00[Asia/Shanghai] # 在某个时间段之间可以访问
测试结果
2025-04-19 16:11:15 到 2025-04-19 16:12:15 时间段之间访问 http://localhost:9527/pay/gateway/get/1 返回成功。
Cookie Route Predicate
spring:
cloud:
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
- Cookie=username,zzyy # cookie中含有username=zzyy可以访问
测试结果
- 原生命令测试
curl http://localhost:9527/pay/gateway/get/1 --cookie “username=zzyy” 返回成功。 - postman测试
请求头中添加 cookie:username=zzyy,访问 http://localhost:9527/pay/gateway/get/1 返回成功。
Header Route Predicate
spring:
cloud:
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
- Header=X-Request-Id,\d+ # 请求头中含有X-Request-Id且值为整数的正则表达式可以访问
测试结果
- 原生命令测试
curl http://localhost:9527/pay/gateway/get/1 -H “X-Request-Id:123” 返回成功。 - postman测试
请求头中添加 X-Request-Id:123,访问 http://localhost:9527/pay/gateway/get/1 返回成功。
Host Route Predicate
spring:
cloud:
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
- Host=**.zzyy.com # 主机地址后必须含有.zzyy.com可以访问
测试结果
- 原生命令测试
curl http://localhost:9527/pay/gateway/get/1 -H “Host:www.zzyy.com” 返回成功。 - postman测试
请求头中添加 Host:www.zzyy.com,访问 http://localhost:9527/pay/gateway/get/1 返回成功。
Query Route Predicate
spring:
cloud:
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
- Query=uid,\d+ # 请求参数必须含有uid且值为整数的正则表达式可以访问
测试结果
访问 http://localhost:9527/pay/gateway/get/1?uid=123 返回成功。
RemoteAddr Route Predicate
spring:
cloud:
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
- RemoteAddr=192.168.42.1/24 # 远程访问地址必须是192.168.42.xx才能访问
测试结果
当前电脑IP为 192.168.42.3,访问 http://192.168.42.3:9527/pay/gateway/get/1 返回成功。
Method Route Predicate
spring:
cloud:
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
- Method=GET,POST # get/post请求可以访问
测试结果
get 请求访问 http://localhost:9527/pay/gateway/get/1 返回成功。
自定义Predicate
网关服务 cloud-gateway9527 中新建 MyRoutePredicateFactory
// 自定义配置会员等级,按照 铂、金、银和yml配置的会员等级,才可以访问
// 继承 AbstractRoutePredicateFactory
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
// 无参构造方法
public MyRoutePredicateFactory() {
super(MyRoutePredicateFactory.Config.class);
}
// 短格式
public List<String> shortcutFieldOrder() {
return Collections.singletonList("userType");
}
// 重写apply方法
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
// 检查request参数中是否存在userType,且值和config中的相同,则可以访问
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
return userType != null && userType.equals(config.getUserType());
}
};
}
// 这个Config类就是路由断言规则
public static class Config {
private @NotNull String userType; // 铂、金、银和yml配置的会员等级
public Config() {}
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
}
}
yml配置
server:
port: 9527
spring:
application:
name: cloud-gateway
# Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
- My=gold # 自定义断言
测试结果
访问 http://localhost:9527/pay/gateway/get/1?userType=gold 返回成功。
自定义Filter
自定义全局Filter统计接口调用耗时情况
网关服务 cloud-gateway9527 中新建 MyGlobalFilter
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {
private static final String START_TIME = "start_time"; // 开始调用方法的时间
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 记录开始时间
exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(START_TIME);
if (startTime != null) {
URI uri = exchange.getRequest().getURI();
log.info("访问接口主机:" + uri.getHost() + ",端口:" + uri.getPort() +
",URL:" + uri.getPath() + ",参数:" + uri.getRawQuery() +
",时长:" + (System.currentTimeMillis() - startTime) + "毫秒");
}
}));
}
// 值越小,优先级越高
@Override
public int getOrder() {
return 0;
}
}
测试结果
访问 http://localhost:9527/pay/gateway/get/1 日志输出:访问接口主机:localhost,端口:9527,URL:/pay/gateway/get/1,参数:null,时长:6毫秒
自定义条件Filter
// 继承 AbstractGatewayFilterFactory
@Component
@Slf4j
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
// 无参构造方法
public MyGatewayFilterFactory() {
super(MyGatewayFilterFactory.Config.class);
}
// 短格式
public List<String> shortcutFieldOrder() {
return Arrays.asList("state");
}
// 重写apply方法
@Override
public GatewayFilter apply(MyGatewayFilterFactory.Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
log.info("进入自定义条件过滤器MyGatewayFilterFactory, state=" + config.getState());
if (request.getQueryParams().containsKey("zzyy")) {
return chain.filter(exchange);
}
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
};
}
public static class Config {
private String state;
public Config() {}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
}
yml配置
server:
port: 9527
spring:
application:
name: cloud-gateway
# Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)
uri: lb://cloud-payment-service # 服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
filters:
- My=zzyy # 自定义条件过滤器
测试结果
访问 http://localhost:9527/pay/gateway/get/1?zzyy=16 返回成功。
总结
以上主要介绍了 Spring Cloud Gateway 路由、断言、过滤的相关知识,以及自定义 Predicate 和 Filter,想了解更多 Spring Cloud Gateway 知识的小伙伴请参考 Spring Cloud Gateway 官网 进行学习,学习更多 Spring Cloud 实战实用技巧的小伙伴,请关注后期发布的文章,认真看完一定能让你有所收获。