微服务网关Spring Cloud Gateway之Predicate

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

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 orparameters.

它属于 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 个,内置断言工厂列表如图 9-10 所示。

image 2025 04 16 18 17 46 204
Figure 1. 图9-10 内置断言工厂列表

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

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

    - After=2025-05-20T08:00:00.000+08:00[Asia/Shanghai]
  • BeforeRoutePredicateFactory:设置时间参数,表示路由配置在指定时间点之前生效。配置格式如下:

    - Before=2035-05-20T08:00:00.000+08:00[Asia/Shanghai]
  • BetweenRoutePredicateFactory:设置时间区间,表示路由配置在指定的时间区间内生效。配置格式如下:

    - Between=2025-05-20T08:00:00.000+08:00[Asia/Shanghai],2035-05-20T
    08: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 的官方文档,见网址5。

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

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

  1. 修改项目名称。

    修改项目名称为 spring-cloud-alibaba-gateway-predicate-demo,之后把各个模块中 pom.xml 文件的 artifactId 修改为 spring-cloud-alibaba-gateway-predicate-demo

  2. 修改基本配置。

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

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

    @PostMapping("/goods/page/{pageNum}")
    public String goodsList(@PathVariable("pageNum") int pageNum) {
    
        // 返回信息给调用端
        return "请求 goodsList,当前服务的端口号为" + applicationServerPort;
    }
  3. 设置路由规则。

    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,^\d+$
    
    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-demogoods-service-demo 项目。如果未能成功启动,则开发人员需要查看控制台中的日志是否报错,并及时确认问题和修复。启动成功后进入 Nacos 控制台,单击 “服务管理” 中的服务列表,可以看到列表中已经存在两个服务的服务信息。

依次使用不同的地址进行测试,结果见表 9-2。

image 2025 04 16 18 30 33 293
Figure 2. 表9-2 使用不同的地址进行测试

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

自定义断言编码实践

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

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

  1. 编写 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;
    
    // 自定义路由断言工厂 处理 goodsId
    @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) {
                    // 获取 goodsId 参数的值
                    String goodsId = exchange.getRequest().getQueryParams().getFirst("goodsId");
    
                    if (null != goodsId) {
                        int numberId = Integer.parseInt(goodsId);
                        // 判断 goodsId 是否在配置区间内
                        if (numberId > config.getMinValue() && numberId < config.getMaxValue()) {
                            // 符合条件,返回 true,路由规则生效
                            return true;
                        }
                    }
                    // 不符合条件,返回 false,路由规则不生效
                    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;
            }
        }
    }

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

  2. 配置自定义断言工厂。

    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
  3. 功能验证。

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

  4. 打开浏览器进行功能验证,依次使用不同的地址进行测试,页面显示内容如图 9-11 所示。结果整理见表 9-3。

image 2025 04 16 18 38 05 277
Figure 3. 图9-11 使用GoodsIdRoutePredicateFactory断言后的请求测试结果
image 2025 04 16 18 38 21 126
Figure 4. 表9-3 使用不同的地址进行测试的结果

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

考虑到读者的知识储备不同,本书中项目的配置文件都是 .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();
}

读者可根据实际需要来完成服务网关项目的配置,虽然三种写法有些区别,但是其底层知识点是一模一样的。