知識庫 / Spring / Spring Cloud RSS 訂閱

Spring Cloud Gateway 路由謂語工廠

Spring Cloud
HongKong
6
01:05 PM · Dec 06 ,2025

1. 簡介

在上一篇文章中,我們已經介紹了 Spring Cloud Gateway 是什麼以及如何使用內置的謂詞(predicates)來實現基本的路由規則。然而,在某些情況下,這些內置的謂詞可能不足以滿足需求。例如,我們的路由邏輯可能需要對數據庫進行查詢。

對於這些情況,Spring Cloud Gateway 允許我們定義自定義的謂詞。 只要定義好這些謂詞,我們就可以像使用任何其他謂詞一樣使用它們,這意味着我們可以使用流式 API(fluent API)和/或 DSL 來定義路由。

2. 謂詞的結構

簡而言之,Spring Cloud Gateway 中的 謂詞 (Predicate) 是一個對象,用於測試給定的請求是否滿足特定條件。對於每個路由,我們可以定義一個或多個謂詞,如果這些謂詞得到滿足,則會接受配置的後端請求,並在應用任何過濾器之後。

在編寫我們的謂詞之前,讓我們先查看現有謂詞或更準確地説,現有 PredicateFactory 的源代碼。正如名稱已經暗示的,Spring Cloud Gateway 使用流行的 工廠方法模式 作為機制,以支持以可擴展的方式創建 Predicate 實例。

我們可以選擇任何一個內置的謂詞工廠,這些工廠可在 org.springframework.cloud.gateway.handler.predicate 包中找到,該包位於 spring-cloud-gateway-core 模塊中。由於它們的名稱都以 RoutePredicateFactory 結尾,因此我們可以輕鬆地發現它們。 HeaderRouterPredicateFactory 是一個很好的例子:

public class HeaderRoutePredicateFactory extends 
  AbstractRoutePredicateFactory<HeaderRoutePredicateFactory.Config> {

    // ... setup code omitted
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange exchange) {
                // ... predicate logic omitted
            }
        };
    }

    @Validated
    public static class Config {
        public Config(boolean isGolden, String customerIdCookie ) {
          // ... constructor details omitted
        }
        // ...getters/setters omitted
    }
}

我們可以觀察到以下幾個關鍵點:

  • 它擴展了 AbstractRoutePredicateFactory<T>,而後者又實現了 gateway 使用的 RoutePredicateFactory 接口
  • apply 方法返回實際 Predicate 的實例——在當前情況下是 GatewayPredicate
  • Predicate 定義了一個內部的 Config 類,用於存儲測試邏輯所使用的靜態配置參數

如果我們查看其他可用的 PredicateFactory,我們會發現基本模式基本上是相同的:

  1. 定義一個 Config 類來存儲配置參數
  2. 擴展 AbstractRoutePredicateFactory,並將配置類用作其模板參數
  3. 覆蓋 apply 方法,並返回一個實現所需測試邏輯的 Predicate

3. 實現自定義謂詞工廠

對於我們的實現,我們假設以下場景:對於給定的API,我們必須在兩個可能的後端之間做出選擇。 “黃金”客户,即我們最重視的客户,應路由到具有更多內存、更多CPU和快速磁盤的強大服務器。 非黃金客户則路由到較弱的服務器,從而導致響應時間更慢。

為了確定請求是否來自黃金客户,我們需要調用一個服務,該服務接受與請求相關的 customerId 並返回其狀態。 至於 customerId,在我們的簡單場景中,我們假設它存儲在cookie中。

有了這些信息,我們現在可以編寫自定義的謂詞。 我們將保持現有的命名約定,併為我們的類命名為 GoldenCustomerRoutePredicateFactory

public class GoldenCustomerRoutePredicateFactory extends 
  AbstractRoutePredicateFactory<GoldenCustomerRoutePredicateFactory.Config> {

    private final GoldenCustomerService goldenCustomerService;
    
    // ... constructor omitted

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {        
        return (ServerWebExchange t) -> {
            List<HttpCookie> cookies = t.getRequest()
              .getCookies()
              .get(config.getCustomerIdCookie());
              
            boolean isGolden; 
            if ( cookies == null || cookies.isEmpty()) {
                isGolden = false;
            } else {                
                String customerId = cookies.get(0).getValue();                
                isGolden = goldenCustomerService.isGoldenCustomer(customerId);
            }              
            return config.isGolden() ? isGolden : !isGolden;           
        };        
    }
    
    @Validated
    public static class Config {        
        boolean isGolden = true;        
        @NotEmpty
        String customerIdCookie = "customerId";
        // ...constructors and mutators omitted   
    }    
}

如我們所見,實現方式相當簡單。我們的 apply 方法返回一個 lambda,它使用傳遞給它的 ServerWebExchange 來實現所需的邏輯。首先,它檢查 customerId cookie 的是否存在。如果找不到它,則這是一個普通客户。否則,它使用 cookie 值來調用 isGoldenCustomer 服務方法。

接下來,我們結合客户端類型和配置的 isGolden 參數以確定返回值。 這允許我們使用相同的謂詞來創建之前描述的相同路由,只需更改 isGolden 參數的值即可

4. 註冊自定義謂語工廠

在編寫自定義謂語工廠後,我們需要讓 Spring Cloud Gateway 知道如何使用它。由於我們使用了 Spring,這通常是通過聲明一個類型為 GoldenCustomerRoutePredicateFactory 的 Bean 來實現的。

由於我們的類型通過其基類 RoutePredicateFactory 實現 RoutePredicateFactory,因此在上下文初始化時會被 Spring 自動發現並提供給 Spring Cloud Gateway。

這裏,我們將使用一個 @Configuration 類來創建我們的 Bean:

@Configuration
public class CustomPredicatesConfig {
    @Bean
    public GoldenCustomerRoutePredicateFactory goldenCustomer(
      GoldenCustomerService goldenCustomerService) {
        return new GoldenCustomerRoutePredicateFactory(goldenCustomerService);
    }
}

我們假設這裏有一個合適的 GoldenCustomerService 實現在 Spring 的上下文中可用。 在我們的例子中,我們有一個簡單的佔位符實現,它將 customerId 值與一個固定的值進行比較——這不現實,但對於演示目的很有用。

5. 使用自定義謂詞

現在我們已經實現了並使“黃金客户”謂詞可用,並且它可供 Spring Cloud Gateway 使用,我們可以開始使用它來定義路由。首先,我們將使用流暢 API 定義一個路由,然後以聲明式方式使用 YAML。

5.1. 使用 Fluent API 定義路由

Fluent API 是一種流行的設計選擇,當我們必須通過編程方式創建複雜對象時。 在我們的例子中,我們使用一個@Bean來定義路由,該Bean創建了一個RouteLocator對象,它使用一個RouteLocatorBuilder和我們的自定義謂詞工廠:

@Bean
public RouteLocator routes(RouteLocatorBuilder builder, GoldenCustomerRoutePredicateFactory gf ) {
    return builder.routes()
      .route("golden_route", r -> r.path("/api/**")
        .uri("https://fastserver")
        .predicate(gf.apply(new Config(true, "customerId"))))
      .route("common_route", r -> r.path("/api/**")
        .uri("https://slowserver")
        .predicate(gf.apply(new Config(false, "customerId"))))                
      .build();
}

請注意我們如何在每個路由中使用兩種不同的 Config 配置。 在第一個案例中,第一個參數設置為 true,因此當收到黃金客户的請求時,謂詞也會評估結果為 true。 至於第二個路由,我們在構造函數中傳遞 false,以便我們的謂詞將返回 true 對於非黃金客户。

5.2. 定義路由(YAML)

我們可以使用屬性或 YAML 文件以聲明式的方式實現與之前相同的結果。這裏我們將使用 YAML,因為它更易於閲讀:

spring:
  cloud:
    gateway:
      routes:
      - id: golden_route
        uri: https://fastserver
        predicates:
        - Path=/api/**
        - GoldenCustomer=true
      - id: common_route
        uri: https://slowserver
        predicates:
        - Path=/api/**
        - name: GoldenCustomer
          args:
            golden: false
            customerIdCookie: customerId

我們已經定義了與之前相同的路由,並使用了兩種可用的選項來定義謂詞。第一個選項,golden_route,採用緊湊表示形式,其形式為Predicate=[param[,param]+]。這裏面的Predicate是謂詞的名稱,它會自動從工廠類名中派生,並移除RoutePredicateFactory後綴。在“=”符號之後,我們有用於填充關聯的Config實例的參數。

這種緊湊的語法在我們的謂詞只需要簡單值時非常有效,但並非總是如此。對於這些場景,我們可以使用長格式,如第二條路由所示。在這種情況下,我們提供一個具有兩個屬性的對象:nameargsname包含謂詞的名稱,args用於填充Config實例。由於這次args是一個對象,因此我們的配置可以根據需要變得儘可能複雜。

6. 測試

現在,讓我們使用 curl 檢查一切是否按預期工作,以測試我們的網關。對於這些測試,我們已經按照之前所示的方式設置了我們的路由,但我們將公開可用的 httpbin.org 服務作為我們的測試後端。這是一個非常實用的服務,我們可以用來快速檢查我們的規則是否按預期工作,它既可以在線使用,也可以作為 Docker 鏡像本地使用。

我們的測試配置還包括標準的 <em>AddRequestHeader</em> 過濾器。我們使用它為請求添加一個自定義 <em>Goldencustomer</em> 標頭,其值與謂詞結果相對應。我們還添加了 <em>StripPrefix</em> 過濾器,因為我們希望在調用後端之前從請求 URI 中刪除 /<em>api</em>

首先,讓我們測試“常用客户端”場景。我們的網關已啓動並運行,我們使用 curl 調用 httpbin<em>headers</em> API,該 API 將簡單地回顯所有收到的標頭:

$ curl http://localhost:8080/api/headers
{
  "headers": {
    "Accept": "*/*",
    "Forwarded": "proto=http;host=\"localhost:8080\";for=\"127.0.0.1:51547\"",
    "Goldencustomer": "false",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.55.1",
    "X-Forwarded-Host": "localhost:8080",
    "X-Forwarded-Prefix": "/api"
  }
}

正如預期的那樣,我們看到 Goldencustomer   標頭被髮送時使用了 false 值。現在我們嘗試使用一個“Golden”客户:

$ curl -b customerId=baeldung http://localhost:8080/api/headers
{
  "headers": {
    "Accept": "*/*",
    "Cookie": "customerId=baeldung",
    "Forwarded": "proto=http;host=\"localhost:8080\";for=\"127.0.0.1:51651\"",
    "Goldencustomer": "true",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.55.1",
    "X-Forwarded-Host": "localhost:8080",
    "X-Forwarded-Prefix": "/api"
  }
}

本次,Goldencustomer 是true的,因為我們已向您發送了一個customerId cookie,其值被我們的測試服務識別為黃金客户的有效值。

7. 結論

在本文中,我們介紹瞭如何將自定義謂語工廠添加到 Spring Cloud Gateway 並使用它們定義使用任意邏輯的路由。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.