知識庫 / Spring / Spring Boot RSS 訂閱

設置 Spring REST API 請求超時時間

REST,Spring Boot
HongKong
9
03:47 AM · Dec 06 ,2025

1. 概述

在本教程中,我們將探討幾種實現 Spring REST API 請求超時時間的可能方法。

然後,我們將討論每種方法的優缺點。請求超時對於防止用户體驗不佳非常有用,尤其是在我們能夠提供默認選項以應對資源耗時過長的情況下。這種設計模式被稱為斷路器模式,但此處我們將不會對其進行詳細説明。

2. <em @Transactional> 時間超期

通過利用 Spring 的 > 註解,我們可以實現數據庫調用上的時間超期。它具有 屬性,我們可以設置其值。該屬性的默認值為 -1,等效於沒有設置任何時間超期。對於時間超期值的外部配置,必須使用不同的屬性,>,而不是它。

例如,假設我們將時間超期設置為 30 秒。如果被註解的方法的執行時間超過這個數字秒數,將會拋出異常。這對於回滾長時間運行的數據庫查詢可能很有用。

為了在實踐中看到這一點,我們將編寫一個非常簡單的 JPA 存儲庫層,它將代表一個花費過多的時間並導致時間超期發生的外接服務。這個 > 擴展包含一個耗時的方法:

public interface BookRepository extends JpaRepository<Book, String> {

    default int wasteTime() {
        Stopwatch watch = Stopwatch.createStarted();

        // delay for 2 seconds
        while (watch.elapsed(SECONDS) < 2) {
          int i = Integer.MIN_VALUE;
          while (i < Integer.MAX_VALUE) {
              i++;
          }
        }
    }
}

如果在帶有 1 秒超時時間的事務中調用我們的 wasteTime() 方法,則超時時間將在方法執行完成之前到期:

@GetMapping("/author/transactional")
@Transactional(timeout = 1)
public String getWithTransactionTimeout(@RequestParam String title) {
    bookRepository.wasteTime();
    return bookRepository.findById(title)
      .map(Book::getAuthor)
      .orElse("No book found for this title.");
}

調用此端點會導致 500 HTTP 錯誤,我們可以將其轉換為更有意義的響應。它也需要很少設置即可實現。

然而,此超時解決方案存在一些缺點:

  • 依賴於具有 Spring 事務管理的數據庫
  • 不適用於整個項目,因為該註解必須存在於每個方法或類中
  • 在超時時間到達時,不會中斷請求,因此實體仍需等待完整的處理時間

讓我們考慮一些替代方案。

3. Resilience4j TimeLimiter

Resilience4j 是一個主要用於管理遠程通信故障容錯的庫。其 TimeLimiter 模塊是我們關注的重點。

首先,我們需要在項目中包含 resilience4j-timelimiter 依賴:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-timelimiter</artifactId>
    <version>2.1.0</version>
</dependency>

接下來,我們將定義一個簡單的 TimeLimiter,其超時時間為 500 毫秒:

private TimeLimiter ourTimeLimiter = TimeLimiter.of(TimeLimiterConfig.custom()
  .timeoutDuration(Duration.ofMillis(500)).build());

我們可以輕鬆地進行外部配置。

我們可以使用我們的 TimeLimiter 來封裝與我們 @Transactional 示例中相同的邏輯:

@GetMapping("/author/resilience4j")
public Callable<String> getWithResilience4jTimeLimiter(@RequestParam String title) {
    return TimeLimiter.decorateFutureSupplier(ourTimeLimiter, () ->
      CompletableFuture.supplyAsync(() -> {
        bookRepository.wasteTime();
        return bookRepository.findById(title)
          .map(Book::getAuthor)
          .orElse("No book found for this title.");
    }));
}

TimeLimiter 相較於 @Transactional 解決方案,提供了諸多優勢。主要體現在它支持亞秒級精度以及對超時響應的即時通知。然而,我們仍然需要在所有需要超時的端點中手動包含它。它還要求大量的封裝代碼,並且產生的錯誤仍然是一個通用的 500 HTTP 錯誤。最後,它需要返回一個 Callable<String> 而不是一個原始的 String。

TimeLimiter 僅包含 Resilience4j 的一部分功能,並能很好地與斷路器模式集成。

4. Spring MVC request-timeout

Spring 提供了名為 spring.mvc.async.request-timeout 的屬性。該屬性允許我們以毫秒級精度定義請求超時時間。

以下是如何定義具有 750 毫秒超時時間的屬性:

spring.mvc.async.request-timeout=750

這個屬性是全局的並且可以外部配置,但和 TimeLimiter 解決方案一樣,它只適用於返回 Callable 類型的端點。 讓我們定義一個類似於 TimeLimiter 示例的端點,但不需要將邏輯封裝在 Futures 中,也不需要提供 TimeLimiter

@GetMapping("/author/mvc-request-timeout")
public Callable<String> getWithMvcRequestTimeout(@RequestParam String title) {
    return () -> {
        bookRepository.wasteTime();
        return bookRepository.findById(title)
          .map(Book::getAuthor)
          .orElse("No book found for this title.");
    };
}

我們能看到代碼更加簡潔,並且 Spring 在我們定義應用程序屬性時會自動實現配置。 一旦超時時間到達,響應會立即返回,並且還會返回更詳細的 503 HTTP 錯誤,而不是泛化的 500 錯誤。 項目中的每個端點都會自動繼承這個超時配置。

現在,讓我們考慮另一種選項,它將允許我們使用更精細的粒度定義超時時間。

5. 配置 HTTP 客户端的超時時間

與其為整個端點設置超時時間,我們可能只想為單個外部調用設置超時時間。 WebClient 是 Spring 的響應式 Web 客户端,允許我們配置響應超時。

Needless to say,所有流行的 HTTP 客户端庫都允許為發出的請求配置自定義超時。例如,Spring 的舊版 RestTemplateWebClient 的非響應式等效項——RestClient,都支持此功能。

5.1. WebClient 超時設置

為了使用 WebClient,我們首先需要在項目中添加 Spring WebFlux 依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    <version>2.4.2</version>
</dependency>

讓我們定義一個具有 250 毫秒響應時間的 WebClient,以便我們通過其基本 URL 在本地主機上調用自身:

@Bean
public WebClient webClient() {
    return WebClient.builder()
      .baseUrl("http://localhost:8080")
      .clientConnector(new ReactorClientHttpConnector(
        HttpClient.create().responseTimeout(Duration.ofMillis(250))
      ))
      .build();
}

顯然,我們可以輕鬆地通過外部配置設置這個超時值。我們還可以外部配置基本 URL,以及其他若干可選屬性。

現在,我們可以將我們的 WebClient 注入到我們的控制器中,並使用它來調用我們自己的 /transactional 端點,該端點仍然具有 1 秒的超時時間。由於我們已將 WebClient 配置為在 250 毫秒內超時,因此我們應該比 1 秒快得多地看到它失敗。

這是我們新的端點:

@GetMapping("/author/webclient")
public String getWithWebClient(@RequestParam String title) {
    return webClient.get()
      .uri(uriBuilder -> uriBuilder
        .path("/author/transactional")
        .queryParam("title", title)
        .build())
      .retrieve()
      .bodyToMono(String.class)
      .block();
}

調用此端點後,我們能看到收到的是 WebClient 的超時時間,以 500 HTTP 錯誤響應的形式呈現。 此外,我們還可以查看日誌以檢查下游的 @Transactional 超時時間,但如果調用外部服務而不是 localhost,則超時時間將遠程打印。

配置不同後端服務用於不同的請求超時時間可能需要,並且可以使用此解決方案實現。 此外,由 WebClient 返回的 MonoFlux 響應包含大量的錯誤處理方法,用於處理通用的超時錯誤響應。

5.2. RestClient 超時設置

Spring 的 RestClient 自 Spring Framework 6 和 Spring Boot 3 引入以來,作為一種更簡單、非反應式的 WebClient 的替代方案。它提供了一種直接、同步的方法,同時仍提供現代且流暢的 API 設計。

首先,如果尚未添加,請添加 spring-boot-starter-web 依賴項:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

同樣地,我們首先將使用構建器模式創建一個 RestClient Bean:

@Bean
public RestClient restClient() {
    return RestClient.builder()
      .baseUrl("http://localhost:" + serverPort)
      .requestFactory(customRequestFactory())
      .build();
}

ClientHttpRequestFactory customRequestFactory() {
    ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
      .withConnectTimeout(Duration.ofMillis(200))
      .withReadTimeout(Duration.ofMillis(200));
    return ClientHttpRequestFactories.get(settings);
}

如我們所見,我們可以通過在 ClientHtttpRequestFactory 中定義 RestClient 的構建器來配置默認超時值。

現在,我們可以使用 HTTP 客户端發出請求,如果在設定的 200 毫秒閾值內未收到響應,則會拋出異常:

@GetMapping("/author/restclient")
public String getWithRestClient(@RequestParam String title) {
    return restClient.get()
      .uri(uriBuilder -> uriBuilder
        .path("/author/transactional")
        .queryParam("title", title)
        .build())
      .retrieve()
      .body(String.class);
}

如我們所見,語法與反應式方法非常相似,這使得我們能夠輕鬆地在兩者之間切換,而無需進行大量的代碼修改。

6. 結論

在本文中,我們探討了多種實現請求超時解決方案。 需要考慮的因素有很多。

如果我們想為數據庫請求設置超時,可以使用 Spring 的 @Transactional 方法及其 timeout 屬性。 如果我們嘗試與更廣泛的熔斷器模式集成,使用 Resilience4j 的 TimeLimiter 會更有意義。 使用 Spring MVC 的 request-timeout 屬性是為所有請求設置全局超時的一種方法,但我們也可以輕鬆地在諸如 WebClientRestClient 這樣的 HTTP 客户端中定義更細粒度的資源級別的超時。

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

發佈 評論

Some HTML is okay.