springboot3 聲明式 HTTP 接口

1 介紹

????????在 Spring 6 和 Spring Boot 3 中,我們可以使用 Java 接口來定義聲明式的遠程 HTTP 服務。這種方法受到 Feign 等流行 HTTP 客戶端庫的啟發,與在 Spring Data 中定義 Repository 的方法類似。

????????聲明式 HTTP 接口包括用于 HTTP exchange 的注解方法。我們可以通過使用帶注解的 Java 接口來簡單地表達遠程 API 的細節,然后讓 Spring 生成實現該接口并執行 exchange 的代理。這有助于減少樣板代碼的編寫。

1.1 Exchange 方法

? ?@HttpExchange?是我們可以應用于 HTTP 接口及其 exchange 方法的根注解。如果我們將其應用于接口層,那么它就會應用于所有 exchange 方法。這對于指定所有接口方法的共同屬性(如 content type 或 URL 前綴)非常有用。所有 HTTP 方法都有對應的注解:

  • @GetExchange?用于 HTTP GET 請求。
  • @PostExchange?用于 HTTP POST 請求。
  • @PutExchange?用于 HTTP PUT 請求。
  • @PatchExchange?用于 HTTP PATCH 請求。
  • @DelectExchange?用于 HTTP DELETE 請求。

????????讓我們使用不同的 HTTP 方法注解,來為遠程 API 定義一個聲明式的 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")

1.2?方法參數

????????在上述示例接口中,我們在方法參數中使用了?@PathVariable?和?@RequestBody?注解。此外,我們還可以為 exchange 方法使用以下參數、注解:

  • URI: 動態設置請求的 URL,覆蓋注解屬性。
  • HttpMethod:動態設置請求的 HTTP 方法,覆蓋注解屬性。
  • @RequestHeader: 添加請求頭信息,參數可以是?Map?或?MultiValueMap
  • @PathVariable:替換請求 URL 中的占位符參數。
  • @RequestBody:提供的請求體可以是要序列化的對象,也可以是響應式流 publisher(如 Mono 或 Flux)。
  • @RequestParam:添加請求參數,參數可以是?Map?或?MultiValueMap
  • @CookieValue:添加 cookie,參數可以是?Map?或?MultiValueMap

????????注意,只有 Content Type 為?application/x-www-form-urlencoded?的請求才會在請求體中對請求參數進行編碼。否則,請求參數將作為 URL 查詢參數添加。

1.3?返回值

????????在我們的示例接口中,exchange 方法返回的是阻塞式的普通值。聲明式 HTTP 接口 exchange 方法既支持阻塞式的返回值,也支持響應式返回值。此外,我們可以選擇只返回特定的響應信息,如狀態碼或響應頭。如果我們對服務響應完全不感興趣,也可以返回?void

????????HTTP 接口 exchange 方法支持以下返回值:

  • voidMono<Void>:執行請求并丟棄響應內容。
  • HttpHeadersMono<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?中注冊的任何其他異步或響應式類型。

2?客戶端代理實現

????????既然我們已經定義了 HTTP 服務接口,就需要創建一個代理來實現該接口并執行 exchange。

2.1?Proxy Factory

????????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 或組件,并用它請求 REST 服務。

2.2?異常處理

????????默認情況下,WebClient?會對任何客戶端或服務器錯誤 HTTP 狀態代碼拋出?WebClientResponseException。我們可以通過注冊一個默認的 response status handler 來自定義異常處理,該 handler 適用于通過客戶端執行的所有響應:

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

????????如此一來,如果我們請求的 book 不存在,我們就會收到一個自定義異常:

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

2.3 項目實戰基本配置

package com.ywz.framework.webClient;import com.ywz.common.SymmetricAlgorithmUtils;
import com.ywz.framework.property.FastAiProperties;
import com.ywz.framework.webClient.service.FastAiChatService;
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
import reactor.netty.http.client.HttpClient;/*** 類描述 -> WebFlux webClient配置類** @Author: ywz* @Date: 2025/04/25*/
@Configuration
@AllArgsConstructor
public class WebClientConfig {private final FastAiProperties fastAiProperties;/*** 方法描述 -> 獲取httpClient客戶端** @Author: ywz* @Date: 2025/04/25*/@Beanpublic HttpClient httpClient() {return HttpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000)  //連接超時.doOnConnected(conn -> {conn.addHandlerLast(new ReadTimeoutHandler(10)); //讀超時conn.addHandlerLast(new WriteTimeoutHandler(10)); //寫超時});}/*** 方法描述 -> 獲取FastAi對話服務接口** @param httpClient httpClient客戶端* @Author: ywz* @Date: 2025/04/25*/@Beanpublic FastAiChatService fastAiChatService(HttpClient httpClient) {// 構建WebClient實例,設置基礎URL為解密后的地址WebClient client = WebClient.builder().baseUrl(SymmetricAlgorithmUtils.decrypt(fastAiProperties.baseUrl()))// 設置默認請求頭,包括內容類型和授權信息.defaultHeaders(headers -> {headers.add("Content-Type", "application/json");headers.add("Authorization", "Bearer " + SymmetricAlgorithmUtils.decrypt(fastAiProperties.yiZhouKey()));})// 使用傳入的httpClient進行請求.clientConnector(new ReactorClientHttpConnector(httpClient))// 配置交換策略,設置最大內存大小.exchangeStrategies(ExchangeStrategies.builder().codecs(clientCodecConfigurer ->clientCodecConfigurer.defaultCodecs().maxInMemorySize(1024 << 8)).build()).build();// 創建HttpServiceProxyFactory,用于生成服務接口的代理HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(WebClientAdapter.create(client)).build();// 返回FastAiChatService接口的實現return factory.createClient(FastAiChatService.class);}
}

3?測試

????????讓我們看看如何測試我們的示例中聲明式 HTTP 接口,以及執行交互的客戶端代理。

3.1?使用?Mockito

????????由于我們的目標是測試使用聲明式 HTTP 接口創建的客戶端代理,因此需要使用 Mockito 的 deep stubbing 功能來模擬底層?WebClient?的 fluent 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());

3.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}"));

????????現在已經準備好了模擬的響應和正在運行的模擬服務器(mock server,),可以調用我們的服務了。

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)
);

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/78582.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/78582.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/78582.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

多級緩存架構設計與實踐經驗

多級緩存架構設計與實踐經驗 在互聯網大廠Java求職者的面試中&#xff0c;經常會被問到關于多級緩存的架構設計和實踐經驗。本文通過一個故事場景來展示這些問題的實際解決方案。 第一輪提問 面試官&#xff1a;馬架構&#xff0c;歡迎來到我們公司的面試現場。請問您對多級…

Mac「brew」快速安裝Redis

安裝Redis 步驟 1&#xff1a;安裝 Redis 打開終端&#xff08;Terminal&#xff09;。 運行以下命令安裝 Redis&#xff1a; brew install redis步驟 2&#xff1a;啟動 Redis 安裝完成后&#xff0c;可以使用以下命令啟動 Redis 服務&#xff1a; brew services start redis…

文獻閱讀(一)植物應對干旱的生理學反應 | The physiology of plant responses to drought

分享一篇Science上的綜述文章&#xff0c;主要探討了植物應對干旱的生理機制&#xff0c;強調通過調控激素信號提升植物耐旱性、保障糧食安全的重要性。 摘要 干旱每年致使農作物產量的損失&#xff0c;比所有病原體造成損失的總和還要多。為適應土壤中的濕度梯度變化&#x…

if consteval

if consteval 是 C23 引入的新特性&#xff0c;該特性是關于immediate function 的&#xff0c;即consteval function。用于在編譯時檢查當前是否處于 立即函數上下文&#xff08;即常量求值環境&#xff09;&#xff0c;并根據結果選擇執行不同的代碼路徑。它是對 std::is_con…

MANIPTRANS:通過殘差學習實現高效的靈巧雙手操作遷移

25年3月來自北京通用 AI 國家重點實驗室、清華大學和北大的論文“ManipTrans: Efficient Dexterous Bimanual Manipulation Transfer via Residual Learning”。 人手在交互中起著核心作用&#xff0c;推動著靈巧機器人操作研究的不斷深入。數據驅動的具身智能算法需要精確、大…

Field訪問對象int字段,對象訪問int字段,通過openjdk17 C++源碼看對象字段訪問原理

在Java反射機制中&#xff0c;訪問對象的int類型字段值&#xff08;如field.getInt(object)&#xff09;的底層實現涉及JVM對內存偏移量的計算與直接內存訪問。本文通過分析OpenJDK 17源碼&#xff0c;揭示這一過程的核心實現邏輯。 一、字段偏移量計算 1. Java層初始化偏移量…

Java查詢數據庫表信息導出Word

參考: POI生成Word多級標題格式_poi設置word標題-CSDN博客 1.概述 使用jdbc查詢數據庫把表信息導出為word文檔, 導出為word時需要下載word模板文件。 已實現數據庫: KingbaseES, 實現代碼: 點擊跳轉 2.效果圖 2.1.生成word內容 所有數據庫合并 數據庫不合并 2.2.生成文件…

Qt中的全局函數講解集合(全)

在頭文件<QtGlobal>中包含了Qt的全局函數&#xff0c;現在就這些全局函數一一詳解。 1.qAbs 原型&#xff1a; template <typename T> T qAbs(const T &t)一個用于計算絕對值的函數。它可以用于計算各種數值類型的絕對值&#xff0c;包括整數、浮點數等 示…

AI與IT協同的典型案例

簡介 本篇代碼示例展示了IT從業者如何與AI協同工作&#xff0c;發揮各自優勢。這些案例均來自2025年的最新企業實踐&#xff0c;涵蓋了不同IT崗位的應用場景。 一、GitHub Copilot生成代碼框架 開發工程師AI協作示例&#xff1a;利用GitHub Copilot生成代碼框架&#xff0c;…

三網通電玩城平臺系統結構與源碼工程詳解(二):Node.js 服務端核心邏輯實現

本篇文章將聚焦服務端游戲邏輯實現&#xff0c;以 Node.js Socket.io 作為主要通信與邏輯處理框架&#xff0c;展開用戶登錄驗證、房間分配、子游戲調度與事件廣播機制的剖析&#xff0c;并附上多個核心代碼段。 一、服務端文件結構概覽 /server/├── index.js …

【prompt是什么?有哪些技巧?】

Prompt&#xff08;提示詞&#xff09;是什么&#xff1f; Prompt 是用戶輸入給AI模型&#xff08;如ChatGPT、GPT-4等&#xff09;的指令或問題&#xff0c;用于引導模型生成符合預期的回答。它的質量直接影響AI的輸出效果。 Prompt 的核心技巧 1. 明確目標&#xff08;Clar…

堆和二叉樹--數據結構初階(3)(C/C++)

文章目錄 前言理論部分堆的模擬實現:(這里舉的大根堆)堆的創建二叉樹的遍歷二叉樹的一些其他功能實現 作業部分 前言 這期的話講解的是堆和二叉樹的理論部分和習題部分 理論部分 二叉樹的幾個性質:1.對于任意一個二叉樹&#xff0c;度為0的節點比度為2的節點多一個 2.對于完全…

Dockerfile講解與示例匯總

容器化技術已經成為應用開發和部署的標準方式,而Docker作為其中的佼佼者,以其輕量、高效、可移植的特性,深受開發者和運維人員的喜愛。本文將從實用角度出發,分享各類常用服務的Docker部署腳本與最佳實踐,希望能幫助各位在容器化之路上少走彎路。 無論你是剛接觸Docker的…

在QGraphicsView中精確地以鼠標為錨縮放圖片

在pyqt中以鼠標所在位置為錨點縮放圖片-CSDN博客中的第一個示例中&#xff0c;通過簡單設置&#xff1a; self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) 使得QGraphicsView具有了以鼠標為錨進行縮放的功能。但是&#xff0c;其內部應當是利用了滾動條的移動來…

制造工廠如何借助電子看板實現高效生產管控

在當今高度競爭的制造業環境中&#xff0c;許多企業正面臨著嚴峻的管理和生產挑戰。首先&#xff0c;管理流程落后&#xff0c;大量工作仍依賴"人治"方式&#xff0c;高層管理者理論知識薄弱且不愿聽取專業意見。其次&#xff0c;生產過程控制能力不足&#xff0c;導…

在 C# .NET 中駕馭 JSON:使用 Newtonsoft.Json 進行解析與 POST 請求實戰

JSON (JavaScript Object Notation) 已經成為現代 Web 應用和服務之間數據交換的通用語言。無論你是開發后端 API、與第三方服務集成&#xff0c;還是處理配置文件&#xff0c;都繞不開 JSON 的解析與生成。在 C# .NET 世界里&#xff0c;處理 JSON 有多種選擇&#xff0c;其中…

Debian10系統安裝,磁盤分區和擴容

1、說明 過程記錄信息有些不全&#xff0c;僅作為參考。如有其它疑問&#xff0c;歡迎留言。 2、ISO下載 地址&#xff1a;debian-10.13.0鏡像地址 3、開始安裝 3.1、選擇圖形界面 3.2、選擇中文語言 3.3、選擇中國區域 3.4、按照提示繼續 3.5、選擇一個網口 3.6、創建管…

1.10軟考系統架構設計師:優秀架構設計師 - 練習題附答案及超詳細解析

優秀架構設計師綜合知識單選題 每道題均附有答案解析&#xff1a; 題目1 衡量優秀系統架構設計師的核心標準不包括以下哪項&#xff1f; A. 技術全面性與底層系統原理理解 B. 能夠獨立完成模塊開發與調試 C. 與利益相關者的高效溝通與協調能力 D. 對業務需求和技術趨勢的戰略…

MPI Code for Ghost Data Exchange in 3D Domain Decomposition with Multi-GPUs

MPI Code for Ghost Data Exchange in 3D Domain Decomposition with Multi-GPUs Here’s a comprehensive MPI code that demonstrates ghost data exchange for a 3D domain decomposition across multiple GPUs. This implementation assumes you’re using CUDA-aware MPI…

計算機考研精煉 計網

第 19 章 計算機網絡體系結構 19.1 基本概念 19.1.1 計算機網絡概述 1.計算機網絡的定義、組成與功能 計算機網絡是一個將分散的、具有獨立功能的計算機系統&#xff0c;通過通信設備與線路連接起來&#xff0c;由功能完善的軟件實現資源共享和信息傳遞的系統。 …