知識庫 / Spring / Spring Web RSS 訂閱

HTTP接口在Spring中

Spring Web
HongKong
6
11:43 AM · Dec 06 ,2025

1. 概述

Spring Framework 6 以及 Spring Boot 版本 3 允許我們使用 Java 接口定義聲明式的 HTTP 服務。這種方法受到了流行的 HTTP 客户端庫(如 Feign)的啓發,與我們在 Spring Data 中定義倉庫的方式類似。

在本教程中,我們首先將瞭解如何定義 HTTP 接口。然後,我們將檢查可用的 Exchange 方法註解,以及支持的方法參數和返回值。接下來,我們將看到如何創建實際的 HTTP 接口實例,即代理客户端,它執行聲明的 HTTP 交換。

最後,我們將檢查如何執行異常處理和測試聲明式 HTTP 接口及其代理客户端。

2. HTTP 接口

該聲明式 HTTP 接口包含對 HTTP 交換的註解方法。我們可以僅使用註解的 Java 接口來表達遠程 API 的詳細信息,並讓 Spring 生成實現該接口並執行交換的代理。 這有助於減少樣板代碼。

2.1. 交換方法

`<em @HttpExchange 是我們可以應用於 HTTP 接口及其交換方法的基礎註解。 如果我們在接口級別應用它,則它將應用於所有交換方法。 這對於指定所有接口方法中常見的屬性(例如內容類型或 URL 前綴)非常有用。

所有 HTTP 方法的附加註解如下:

  • 用於 HTTP GET 請求
  • 用於 HTTP POST 請求
  • 用於 HTTP PUT 請求
  • 用於 HTTP PATCH 請求
  • 用於 HTTP DELETE 請求

讓我們使用方法特定的註解來定義一個簡單的 REST 服務中的聲明式 HTTP 接口:

interface BooksService {

    @GetExchange("/books")
    List<Book> getBooks();

    @GetExchange("/books/{id}")
    Book getBook(@PathVariable long id);

    @PostExchange("/books")
    Book saveBook(@RequestBody Book book);

    @DeleteExchange("/books/{id}")
    ResponseEntity<Void> deleteBook(@PathVariable long id);
}

我們應指出,所有 HTTP 方法特定的註解都已元註解化為 @HttpExchange。因此,@GetExchange(“/books”) 等效於 @HttpExchange(url = “/books”, method = “GET”)

2.2. 方法參數

在我們的示例接口中,我們使用了 <em @PathVariable</em><em @RequestBody</em> 註解用於方法參數。此外,我們還可以使用以下方法參數集來處理我們的交換方法:

  • URI:動態設置請求的 URL,覆蓋註解屬性
  • HttpMethod:動態設置請求的 HTTP 方法,覆蓋註解屬性
  • :添加請求頭名和值,參數可以是
  • :替換請求 URL 中佔位符的值
  • :提供請求體,可以是需要序列化的對象,或者如 這樣的反應式流發佈器
  • :添加請求參數名和值,參數可以是
  • :添加 cookie 名和值,參數可以是

需要注意的是,請求參數僅編碼在內容類型為 <em “application/x-www-form-urlencoded”</em> 的請求體中。 否則,請求參數作為 URL 查詢參數添加。

2.3. 返回值

在我們的示例接口中,交換方法返回阻塞值。但是,聲明式的 HTTP 接口 交換方法 支持阻塞和反應式 返回值

此外,我們可能選擇只返回特定的響應信息,例如狀態碼或標頭。 此外,如果對服務響應完全不感興趣,則返回 void

總結一下,HTTP 接口交換方法支持以下返回值集:

  • void, Mono<Void>: 執行請求並釋放響應內容
  • HttpHeaders, Mono<HttpHeaders>: 執行請求,釋放響應內容,並返回響應標頭
  • <T>, Mono<T>: 執行請求並將響應內容解碼為聲明的類型
  • <T>, Flux<T>: 執行請求並將響應內容解碼為聲明類型的流
  • ResponseEntity<Void>, Mono<ResponseEntity<Void>>: 執行請求,釋放響應內容,並返回一個 ResponseEntity,包含狀態和標頭
  • ResponseEntity<T>, Mono<ResponseEntity<T>>: 執行請求,釋放響應內容,並返回一個 ResponseEntity,包含狀態、標頭和解碼後的主體
  • Mono<ResponseEntity<Flux<T>>>: 執行請求,釋放響應內容,並返回一個 ResponseEntity,包含狀態、標頭和解碼後的響應主體流

我們還可以使用在 ReactiveAdapterRegistry 中註冊的任何其他異步或反應式類型。

3. 客户端代理

現在我們已經定義了樣品的 HTTP 服務接口,需要創建一個代理,該代理實現接口並執行交換操作。

3.1. 代理工廠

Spring 框架提供了 HttpServiceProxyFactory,我們可以利用它來為我們的 HTTP 接口生成客户端代理。

HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory
  .builder(WebClientAdapter.forClient(webClient))
  .build();
booksService = httpServiceProxyFactory.createClient(BooksService.class);

為了使用提供的工廠創建代理,除了 HTTP 接口,我們還需要一個反應式 Web 客户端實例:

WebClient webClient = WebClient.builder()
  .baseUrl(serviceUrl)
  .build();

現在,我們可以將客户端代理實例註冊為 Spring Bean 或 Component,並使用它與 REST 服務進行數據交換。

3.2. 異常處理

默認情況下,<em >WebClient</em> 在任何客户端或服務器錯誤 HTTP 狀態碼下都會拋出 <em >WebClientResponseException</em>。我們可以通過註冊一個默認響應狀態處理器來自定義異常處理,該處理器將應用於通過客户端執行的所有響應:

BooksClient booksClient = new BooksClient(WebClient.builder()
  .defaultStatusHandler(HttpStatusCode::isError, resp ->
    Mono.just(new MyServiceException("Custom exception")))
  .baseUrl(serviceUrl)
  .build());

因此,如果請求一本不存在的書籍,我們將收到一個自定義異常:

BooksService booksService = booksClient.getBooksService();
assertThrows(MyServiceException.class, () -> booksService.getBook(9));

4. 測試

讓我們看看如何測試我們的樣子的聲明式 HTTP 接口以及執行交換的客户端代理。

4.1. 使用 Mockito

為了測試使用我們聲明式 HTTP 接口創建的客户端代理,我們需要使用 Mockito 的深度樁 (deep stubbing) 功能,來 模擬底層 WebClient 的 流式 API。

@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private WebClient webClient;

現在,我們可以使用 Mockito 的 BDD 方法來調用鏈式 WebClient 方法並提供一個模擬的響應:

given(webClient.method(HttpMethod.GET)
  .uri(anyString(), anyMap())
  .retrieve()
  .bodyToMono(new ParameterizedTypeReference<List<Book>>(){}))
  .willReturn(Mono.just(List.of(
    new Book(1,"Book_1", "Author_1", 1998),
    new Book(2, "Book_2", "Author_2", 1999)
  )));

一旦我們搭建好模擬響應,就可以使用 HTTP 接口中定義的這些方法來調用我們的服務:

BooksService booksService = booksClient.getBooksService();
Book book = booksService.getBook(1);
assertEquals("Book_1", book.title());

4.2. 使用 MockServer

如果需要避免對 WebClient 進行模擬,我們可以使用像 MockServer 這樣的庫來生成並返回固定 HTTP 響應:

new MockServerClient(SERVER_ADDRESS, serverPort)
  .when(
    request()
      .withPath(PATH + "/1")
      .withMethod(HttpMethod.GET.name()),
    exactly(1)
  )
  .respond(
    response()
      .withStatusCode(HttpStatus.SC_OK)
      .withContentType(MediaType.APPLICATION_JSON)
      .withBody("{\"id\":1,\"title\":\"Book_1\",\"author\":\"Author_1\",\"year\":1998}")
  );

現在我們已經有了模擬響應以及運行的模擬服務器,就可以調用我們的服務:

BooksClient booksClient = new BooksClient(WebClient.builder()
  .baseUrl(serviceUrl)
  .build());
BooksService booksService = booksClient.getBooksService();
Book book = booksService.getBook(1);
assertEquals("Book_1", book.title());

此外,我們還可以驗證我們的測試代碼是否正確調用了正確的模擬服務:

mockServer.verify(
  HttpRequest.request()
    .withMethod(HttpMethod.GET.name())
    .withPath(PATH + "/1"),
  VerificationTimes.exactly(1)
);

5. 結論

在本文中,我們探討了 Spring 6 中提供的聲明式 HTTP 服務接口。我們研究瞭如何使用可用的 exchange 方法註解定義 HTTP 接口,以及支持的參數和返回值。

我們研究瞭如何創建實現 HTTP 接口並執行交換的代理客户端。此外,我們還了解了如何通過定義自定義狀態處理程序來執行異常處理。最後,我們使用 MockitoMockServer 測試了聲明式接口及其客户端代理。

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

發佈 評論

Some HTML is okay.