服务网关Spring Cloud Gateway之Predicate(断言)

2,404 阅读8分钟

本章节的讲解代码下载地址如下:

链接: https://pan.baidu.com/s/1PdShp68-iUHgXGR4Gs4Kiw 
提取码: 189s

Spring Cloud Gateway 官方文档中对断言的定义如下:

This is a Java 8 Function Predicate. The input type is a Spring Framework ServerWebExchange. This lets you match on anything from the HTTP request, such as headers or parameters.

它属于 Java8 语言中的 Predicate 函数(Predicate 可以翻译为谓词、断言,不同的中文文档中叫法可能不同),参数为 ServerWebExchange 对象。可以让开发者匹配到任意的 HTTP 请求,不论是通过请求头还是请求参数。

** 简单理解的话,Spring Cloud Gateway 中的 Predicate 配置就是一个条件判断工具。** 开发者在服务网关项目中配置之后,可以使用它来验证接收到的请求,如果符合当前配置的规则则通过验证,进而使得服务网关将该请求路由到微服务中。如果不符合当前配置的规则,就无法通过验证,就不会将当前请求路由到微服务中,而是返回错误信息。

在配置文件中,断言的配置项为 spring.cloud.gateway.routes.predicates,可以配置一个断言,即满足一个条件则路由配置生效。也可以配置多个断言,需要同时满足多个条件路由配置才会生效。比如前文中的 Path 就是一个断言配置,Path=/goods/** 就使用了内置的 PathRoutePredicateFactory 断言工厂,表示访问到网关项目的路径是以 / goods 开头,则路由配置生效。

Spring Cloud Gateway 内置断言工厂

Spring Cloud Gateway 中提供了很多的内置断言实现供开发者直接使用,能够帮助开发者实现不同的路由配置。

内置断言工厂列表及功能

这些内置的断言工厂类都实现了 AbstractRoutePredicateFactory 抽象类,在 3.1.1 版本中共有 14 个,列表如下图所示。

常用的断言工厂介绍如下。

AfterRoutePredicateFactory:设置时间参数,表示路由配置会在指定时间点之后生效。配置格式如下所示:

 - After=2025-05-20T08:00:00.000+08:00[Asia/Shanghai]


BeforeRoutePredicateFactory:设置时间参数,表示路由配置会在指定时间点之前生效。配置格式如下所示:

 - Before=2035-05-20T08:00:00.000+08:00[Asia/Shanghai]


BeteweenRoutePredicateFactory:设置时间区间,表示路由配置会在指定的时间区间内生效。配置格式如下所示:

 - Between=2025-05-20T08:00:00.000+08:00[Asia/Shanghai],2035-05-20T08:00:00.000+08:00[Asia/Shanghai]


CookieRoutePredicateFactory:设置 cookie 名称和 cookie 值的正则表达式,表示路由配置会在匹配该 cookie 配置后生效。配置格式如下所示:

 - Cookie=myCookie,newbee*


HeaderRoutePredicateFactory:设置请求 header 名称和 header 值的正则表达式,表示路由配置会在匹配该请求 header 配置后生效。配置格式如下所示:

 - Header=token,newbee*


HostRoutePredicateFactory:设置请求 host,表示路由配置会在请求的 host 符合条件后生效,多个 host 的话以逗号分开。配置格式如下所示:

 - Host=**.newbee.ltd,**.newbee.com


MethodRoutePredicateFactory:设置请求方法,表示路由配置会在请求方法符合条件后生效。配置格式如下所示:

 - Method=POST,GET


PathRoutePredicateFactory:设置请求路径规则,表示路由配置会在匹配该请求路径配置后生效,有多个规则的话以逗号分开。配置格式如下所示:

用 - Path=/goods/**


QueryRoutePredicateFactory:设置请求参数和参数值的正则表达式,表示路由配置会在请求参数符合条件后生效。配置格式如下所示:

 - Query=goodsName,iPhone.


这些断言工厂实现主要是针对请求的时间信息以及请求中的地址信息、参数信息设定特定的规则,以此来判断当前的路由规则是否生效。更多内容可以参考 Spring Cloud Gateway 的官方文档,地址如下:

github.com/spring-clou…

使用内置断言工厂配置路由规则

Spring Cloud Gateway 的内置断言介绍完毕,接下来笔者会使用内置的 MethodRoutePredicateFactory 和 QueryRoutePredicateFactory 编写一个 demo 演示它们的作用。本章节代码是在 spring-cloud-alibaba-gateway-demo 项目的基础上修改的,具体步骤如下所示。

第一步,修改文件名称。

首先,修改项目名称为 spring-cloud-alibaba-gateway-predicate-demo,然后把各个 Module 中 pom.xml 文件的 artifactId 修改为 spring-cloud-alibaba-gateway-predicate-demo。

第二步,修改基本配置。

为了做章节区分,这里就把 gateway-demo 和 gateway-demo2 项目中的端口号修改为 8137 和 8139,并且在 gateway-demo 项目配置文件中添加上注册中心的配置。

修改 goods-service-demo 项目中 goodsList 接口的请求方式为 POST,代码如下:

@PostMapping("/goods/page/{pageNum}")
public String goodsList(@PathVariable("pageNum") int pageNum) {
  
  return  "请求goodsList,当前服务的端口号为" + applicationServerPort;
}


第三步,设置路由规则。

除了 Path 路径规则外,分别增加参数规则和请求方法的规则,代码如下:

spring.cloud.gateway.routes[0].id=goods-service-route
spring.cloud.gateway.routes[0].uri=lb://newbee-cloud-goods-service
spring.cloud.gateway.routes[0].order=1
spring.cloud.gateway.routes[0].predicates[0]=Path=/goods
#goodsId参数必须为数字
spring.cloud.gateway.routes[0].predicates[1]=Query=goodsId,^\+?[1-9][0-9]*$

spring.cloud.gateway.routes[1].id=goods-service-route2
spring.cloud.gateway.routes[1].uri=lb://newbee-cloud-goods-service
spring.cloud.gateway.routes[1].order=0
#路径以/goods/page/开头的请求,其请求方法必须是POST方式
spring.cloud.gateway.routes[1].predicates[0]=Path=/goods/page/**
spring.cloud.gateway.routes[1].predicates[1]=Method=POST


上述断言配置分别表示请求路径为 / goods 的接口时必须包含 goodsId 参数,且参数值为数字。请求路径是以 / goods/page / 开头的话,其请求方法必须是 POST 方式。

功能验证

接下来,需要启动 Nacos Server,然后依次启动 gateway-demo 和 goods-service-demo 项目。如果未能成功启动,开发者需要查看控制台中的日志是否报错,并及时确认问题和修复。启动成功后进入 Nacos 控制台,点击 “服务管理” 中的服务列表,可以看到列表中已经存在两个服务的服务信息。

接着,依次使用不同的地址进行测试,结果如下。

请求 URL是否正确路由响应结果
http://localhost:8137/goods?goodsId=aaaThere was an unexpected error (type=Not Found, status=404).
http://localhost:8137/goods?goodsId=520商品 520,当前服务的端口号为 8130
http://localhost:8137/goods/page/1 GETThere was an unexpected error (type=Not Found, status=404).
http://localhost:8137/goods/page/1 POST请求 goodsList,当前服务的端口号为 8130

还有其它内置断言工厂可供使用,篇幅所限就不再一一举例了,读者可以自行对照前文中介绍的内置断言工厂进行编码和测试。

自定义断言编码实践

Spring Cloud Gateway 已经提供了非常丰富和功能完善的断言工厂供开发者使用。不过,除了使用内置的断言工厂外,开发者们也可以根据具体的业务需求,自定义断言工厂并进行配置。

自定义断言工厂的编码也不复杂,前文中介绍的内置断言工厂都实现了实现了 AbstractRoutePredicateFactory 抽象类,命名方式为 xxxRoutePredicateFactory,然后在配置文件中写上 - xxx 即可。根据这几个固定的写法,可以自行实现一个断言工厂类。比如只允许查询 goodsId 为 10000 到 100000 之间的商品数据,以下为具体的实现步骤。

第一步,编写 GoodsIdRoutePredicateFactory。

在 gateway-demo2 项目中,新建 ltd.gateway.cloud.newbee.predicate 包,并新建 GoodsIdRoutePredicateFactory.java 文件,具体代码及注释如下:

package ltd.gateway.cloud.newbee.predicate;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;


@Component
public class GoodsIdRoutePredicateFactory extends AbstractRoutePredicateFactory<GoodsIdRoutePredicateFactory.Config> {

    public GoodsIdRoutePredicateFactory() { 
        super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        
        return Arrays.asList("minValue", "maxValue");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange exchange) {
                
                String goodsId = exchange.getRequest().getQueryParams().getFirst("goodsId");
                if (null != goodsId) {
                    int numberId = Integer.parseInt(goodsId);
                    
                    if (numberId > config.getMinValue() && numberId < config.getMaxValue()) {
                        
                        return true;
                    }
                }
                
                return false;
            }

        };
    }

    @Validated
    
    public static class Config {

        private int minValue;

        private int maxValue;

        public int getMinValue() {
            return minValue;
        }

        public void setMinValue(int minValue) {
            this.minValue = minValue;
        }

        public int getMaxValue() {
            return maxValue;
        }

        public void setMaxValue(int maxValue) {
            this.maxValue = maxValue;
        }
    }
}


在该类中,分别定义了配置文件中定义的区间参数 minValue 和 maxValue。在 test() 方法中是具体的判断逻辑,获取 goodsId 参数值然后判断是否在配置的区间内。符合条件,则返回 true,路由规则生效。不符合条件,则返回 false,路由规则不生效。

第二步,配置自定义断言工厂。

接下来,在 application.properties 配置文件中,配置这个自定义的断言工厂。路由配置如下:

spring.cloud.gateway.routes[0].id=goods-service-route
spring.cloud.gateway.routes[0].uri=lb://newbee-cloud-goods-service
spring.cloud.gateway.routes[0].order=1
spring.cloud.gateway.routes[0].predicates[0]=Path=/goods
# 自定义断言配置,配置项为goodsId,最大值为100000,最小值为10000
spring.cloud.gateway.routes[0].predicates[1]=GoodsId=10000,100000


第三步,功能验证。

编码完成后,需要启动 Nacos Server,然后依次启动 gateway-demo2 和 goods-service-demo 项目。如果未能成功启动,开发者需要查看控制台中的日志是否报错,并及时确认问题和修复。启动成功后进入 Nacos 控制台,点击 “服务管理” 中的服务列表,可以看到列表中已经存在两个服务的服务信息。

最后,打开浏览器进行功能验证,依次使用不同的地址进行测试,页面显示内容如下图所示。

请求 URL是否正确路由简单分析
http://localhost:8139/goods?goodsId=aaa不是数字
http://localhost:8139/goods?goodsId=520数字小于 10000
http://localhost:8139/goods?goodsId=11520数字在配置区间内
http://localhost:8139/goods?goodsId=111520数字大于 100000

自定义断言工厂验证成功!

考虑到读者的知识储备不同,所以本课程中项目的配置文件都是. properties 格式的,这种方式最简单也最好理解。除了这种写法外,也可以使用 yml 配置文件进行网关路由的配置。当然,也可以使用 Java 代码来声明路由,写法如下:

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder, ThrottleGatewayFilterFactory throttle) {
    return builder.routes()
            .route(r -> r.host("**.abc.org").and().path("/image/png")
                .uri("http://httpbin.org:80")
            )
            .route(r -> r.path("/image/webp")
                .uri("http://httpbin.org:80")
            )
            .route(r -> r.order(-1)
                .host("**.throttle.org").and().path("/get")
                .uri("http://httpbin.org:80")
            )
            .build();
}


读者可根据实际需要来完成服务网关项目的配置,虽然三种写法有些区别,但是其底层知识点是一模一样的。当然,读者如果有任何问题或者想要和笔者讨论的内容,都可以在评论区留下看法,笔者会根据读者的反馈和问题继续整理和完善本章节内容。

原文地址 juejin.cn