基于 SpringBoot 的 REST API 與 RPC 調用的統一封裝

一、為何需要統一封裝?

在討論統一封裝之前,我們先看看 REST 和 RPC 各自的適用場景。

REST API 基于 HTTP 協議,采用 JSON 作為數據交換格式,可讀性好且跨語言,非常適合對外提供服務。

RPC(如 Dubbo、gRPC)采用二進制協議(如 Protobuf),序列化效率高、網絡開銷小,適合內部微服務間的高頻調用。

實際項目中,不同服務可能提供了不同的通信協議,帶來服務間調用方式的不一致,帶來編碼及后續維護的復雜度。

二、設計思路:基于外觀模式的統一調用層

解決這個問題的關鍵是引入 外觀模式(Facade Pattern) ,通過一個統一的外觀類封裝所有調用細節。

同時結合適配器模式和策略模式,實現不同協議的無縫切換。

2.1 核心設計

整個設計分為三層:

統一接口層:定義通用調用接口,屏蔽底層差異
協議適配層:實現 REST 和 RPC 的具體調用邏輯
業務邏輯層:業務服務實現,完全不用關心調用方式

2.2 關鍵設計模式

外觀模式:提供統一入口 UnifiedServiceClient,封裝所有調用細節
適配器模式:將 RestTemplateDubboReference 適配為統一接口
策略模式:根據配置動態選擇調用方式(REST 或 RPC)

三、實現步驟:從統一響應到協議適配

3.1 統一響應體設計

首先要解決的是返回格式不一致問題。我們定義了統一的響應體 ApiResponse

@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResponse<T> implements Serializable {private String code;       // 狀態碼private String message;    // 消息提示private T data;            // 業務數據private long timestamp;    // 時間戳// 成功響應public static <T> ApiResponse<T> success(T data) {return ApiResponse.<T>builder().code("200").message("success").data(data).timestamp(System.currentTimeMillis()).build();}// 失敗響應public static <T> ApiResponse<T> fail(String code, String message) {return ApiResponse.<T>builder().code(code).message(message).timestamp(System.currentTimeMillis()).build();}
}

對于 REST 接口,通過 @RestControllerAdvice 實現自動封裝

@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return true; // 對所有響應生效}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType,ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof ApiResponse) {return body; // 已封裝的直接返回}return ApiResponse.success(body); // 未封裝的自動包裝}
}

3.2 統一異常處理

異常處理同樣需要統一。對于 REST 接口,使用 @ControllerAdvice

@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(BusinessException.class)public ApiResponse<Void> handleBusinessException(BusinessException e) {return ApiResponse.fail(e.getCode(), e.getMessage());}@ExceptionHandler(Exception.class)public ApiResponse<Void> handleException(Exception e) {log.error("系統異常", e);return ApiResponse.fail("500", "系統內部錯誤");}
}

對于 Dubbo RPC,通過自定義過濾器實現異常轉換:

package com.example.unified;import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.example.unified.exception.BusinessException;
import org.apache.dubbo.rpc.*;import java.util.function.BiConsumer;@Activate(group = Constants.PROVIDER)
public class DubboExceptionFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {try {AsyncRpcResult result = (AsyncRpcResult )invoker.invoke(invocation);if (result.hasException()) {Throwable exception = result.getException();if (exception instanceof BusinessException) {BusinessException e = (BusinessException) exception;return new AppResponse (ApiResponse.fail(e.getCode(), e.getMessage()));}}return result.whenCompleteWithContext(new BiConsumer<Result, Throwable>() {@Overridepublic void accept(Result result, Throwable throwable) {result.setValue(ApiResponse.success(result.getValue()));}});} catch (Exception e) {return new AppResponse (ApiResponse.fail("500", "RPC調用異常"));}}
}

3.3 協議適配層實現

定義統一調用接口 ServiceInvoker

package com.example.unified.invoker;import cn.hutool.core.lang.TypeReference;
import com.example.unified.ApiResponse;public interface ServiceInvoker {<T> ApiResponse<T> invoke(String serviceName, String method, Object param, TypeReference<ApiResponse<T>> resultType);
}

然后分別實現 REST 和 RPC 適配器:

REST 適配器

package com.example.unified.invoker;import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import com.example.unified.ApiResponse;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;@Component
public class RestServiceInvoker implements ServiceInvoker {private final RestTemplate restTemplate;private Environment environment;public RestServiceInvoker(RestTemplate restTemplate,Environment environment) {this.restTemplate = restTemplate;this.environment = environment;}@Overridepublic <T> ApiResponse<T> invoke(String serviceName, String method, Object param, TypeReference<ApiResponse<T>> resultType) {String serviceUrl = environment.getProperty("service.direct-url." + serviceName);String url = serviceUrl + "/" + method;HttpEntity request = new HttpEntity<>(param);String result = restTemplate.postForObject(url, request, String.class);return JSONUtil.toBean(result, resultType, true);}
}

Dubbo 適配器

package com.example.unified.invoker;import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.unified.ApiResponse;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.utils.SimpleReferenceCache;
import org.apache.dubbo.rpc.service.GenericService;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
public class DubboServiceInvoker implements ServiceInvoker {private final SimpleReferenceCache referenceCache;private final Environment environment;public DubboServiceInvoker(SimpleReferenceCache referenceCache, Environment environment) {this.referenceCache = referenceCache;this.environment = environment;}@Overridepublic <T> ApiResponse<T> invoke(String serviceName, String method, Object param,TypeReference<ApiResponse<T>> resultType) {ReferenceConfig<GenericService> reference = new ReferenceConfig<>();String interfaceName = environment.getProperty("dubbo.reference." + serviceName + ".interfaceName");reference.setInterface(interfaceName);reference.setGeneric("true");reference.setRegistry(new RegistryConfig("N/A"));reference.setVersion("1.0.0");// 從配置文件讀取直連地址(優先級:代碼 > 配置文件)String directUrl = environment.getProperty("dubbo.reference." + serviceName + ".url");if (StrUtil.isNotEmpty(directUrl)) {reference.setUrl(directUrl);  // 設置直連地址,覆蓋注冊中心發現}GenericService service = referenceCache.get(reference);Object[] params = {param};Object result = service.$invoke(method, getParamTypes(params), params);JSONObject jsonObject = new JSONObject(result);ApiResponse<T> response = JSONUtil.toBean(jsonObject, resultType,true);return response;}private String[] getParamTypes(Object[] params) {return Arrays.stream(params).map(p -> p.getClass().getName()).toArray(String[]::new);}
}

3.4 外觀類與策略選擇

最后實現外觀類 UnifiedServiceClient

package com.example.unified;import cn.hutool.core.lang.TypeReference;
import com.example.unified.config.ServiceConfig;
import com.example.unified.invoker.ServiceInvoker;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;@Component
public class UnifiedServiceClient {private final Map<String, ServiceInvoker> invokerMap;private final ServiceConfig serviceConfig;public UnifiedServiceClient(List<ServiceInvoker> invokers, ServiceConfig serviceConfig) {this.invokerMap = invokers.stream().collect(Collectors.toMap(invoker -> invoker.getClass().getSimpleName(), Function.identity()));this.serviceConfig = serviceConfig;}public <T> ApiResponse<T> call(String serviceName, String method, Object param, TypeReference<ApiResponse<T>> resultType) {// 根據配置選擇調用方式String protocol = serviceConfig.getProtocol(serviceName);ServiceInvoker invoker = protocol.equals("rpc") ? invokerMap.get("DubboServiceInvoker") : invokerMap.get("RestServiceInvoker");return invoker.invoke(serviceName, method, param,resultType);}
}

服務調用方式通過配置文件指定:

service:direct-url: # 直連地址配置user-service: http://localhost:8080/user  # 訂單服務REST地址config:user-service: rest    # 用戶服務用rest調用order-service: rpc  # 訂單服務用RPC調用# Dubbo 配置(若使用 Dubbo RPC)
dubbo:application:name: unified-client-demo  # 當前應用名
#    serialize-check-status: DISABLEqos-enable: falseregistry:address: N/Areference:# 為指定服務配置直連地址(無需注冊中心)order-service:interfaceName: com.example.unified.service.OrderService  # 服務接口名稱url: dubbo://192.168.17.1:20880  # 格式:dubbo://IP:端口protocol:name: dubbo    # RPC 協議名稱port: 20880    # 端口

四、使用案例

package com.example.unified.controller;import cn.hutool.core.lang.TypeReference;
import com.example.unified.ApiResponse;
import com.example.unified.UnifiedServiceClient;
import com.example.unified.dto.OrderDTO;
import com.example.unified.dto.UserDTO;
import com.example.unified.service.OrderService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api")
public class DemoController {@Autowiredprivate UnifiedServiceClient serviceClient;@RequestMapping("/user")public ApiResponse<UserDTO> getUser(@RequestBody UserDTO qryUserDTO) {ApiResponse<UserDTO> response = serviceClient.call("user-service", "getUser", qryUserDTO, new TypeReference<ApiResponse<UserDTO>>() {});return response;}@RequestMapping("/order")public ApiResponse<OrderDTO> getOrder(@RequestBody OrderDTO qryOrderDTO) {ApiResponse<OrderDTO> response = serviceClient.call("order-service", "getOrder", qryOrderDTO, new TypeReference<ApiResponse<OrderDTO>>() {});String status = response.getData().getStatus();System.err.println("status:" + status);return response;}
}

六、總結

通過外觀模式 + 適配器模式 + 策略模式的組合,實現了 REST API 與 RPC 調用的統一封裝。

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

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

相關文章

【SpringBoot】 整合MyBatis+Postgresql

MyBatis 是一個輕量級的持久化框架&#xff0c;用于簡化數據庫訪問和操作。它通過將 SQL 語句與 Java 代碼分離&#xff0c;允許開發者使用 XML 或注解來配置 SQL 語句&#xff0c;并將結果映射為 Java 對象。MyBatis 提供了靈活的 SQL 控制&#xff0c;適合需要精細控制 SQL 的…

無縫銜接直播流體驗

文章目錄前言&#x1f9e0; 1. 為什么能“無縫銜接”&#xff1f;&#x1f9f0; 2. Flutter 實現方案? 總體策略&#x1f3af; 核心技術點? a. 使用全局播放器管理器&#xff08;單例模式&#xff09;? b. 廣場頁中的直播卡片使用播放器? c. 詳情頁復用控制器? d. 頁面切換…

[論文閱讀] 軟件工程 | 首個德語軟件工程情感分析黃金標準數據集:構建與價值解析

首個德語軟件工程情感分析黃金標準數據集&#xff1a;構建與價值解析 論文標題&#xff1a;A German Gold-Standard Dataset for Sentiment Analysis in Software EngineeringarXiv:2507.07325 A German Gold-Standard Dataset for Sentiment Analysis in Software Engineering…

PyTorch編程實踐:一文就入門的上手開發!

引言 PyTorch作為當今深度學習領域最流行的框架之一&#xff0c;以其動態計算圖、直觀的Python接口和強大的GPU加速能力&#xff0c;贏得了眾多研究人員和工程師的青睞。本文將深入探討PyTorch的編程實踐&#xff0c;從基礎概念到高級應用&#xff0c;幫助讀者全面掌握這一強大…

關于學習docker中遇到的問題

Cannot connect to the Docker daemon at unix:///home/pc/.docker/desktop/docker.sock. Is the docker daemon running?如何配置新的路徑 #運行這條命令&#xff0c;查看docker狀態 sudo systemctl status docker如圖所示表示監聽路徑不對&#xff0c;因此修改路徑即可&…

無法打開windows安全中心解決方案

系統還原或重置&#xff1a;如果以上方法均無效&#xff0c;可嘗試系統還原&#xff0c;使用之前創建的還原點恢復系統。或在設置中選擇 “系統> 恢復 > 重置此電腦”&#xff0c;選擇 “保留我的文件” 以避免數據丟失。創建新用戶賬戶&#xff1a;按下 Win I 打開設置…

復習筆記 33

緒論 《幻術》 張葉蕾 我該怎么承認&#xff0c; 一切都是幻境。 函數的基本性質和無窮小量及其階的比較 我感覺強化課我要跟上的話&#xff0c;我需要把基礎&#xff0c;強化的講義&#xff0c;還有練習冊上面的所有題都刷爛。不然我感覺自己考 140 完全就是癡人說夢。搞笑呢。…

算法學習筆記:12.快速排序 ——從原理到實戰,涵蓋 LeetCode 與考研 408 例題

快速排序是計算機科學中最經典的排序算法之一&#xff0c;由 Tony Hoare 在 1960 年提出。它憑借平均時間復雜度 O (nlogn)、原地排序&#xff08;空間復雜度 O (logn)&#xff0c;主要來自遞歸棧&#xff09;以及良好的實際性能&#xff0c;成為工業界處理大規模數據排序的首選…

unity 有打擊感的圖片,怎么做動畫,可以表現出良好的打擊效果

完整實現腳本:using UnityEngine; using UnityEngine.UI; using System.Collections;[RequireComponent(typeof(Image))] public class HitEffectController : MonoBehaviour {[Header("基礎設置")]public float hitDuration 0.5f; // 打擊效果總時長[Header("…

cuda編程筆記(7)--多GPU上的CUDA

零拷貝內存 在流中&#xff0c;我們介紹了cudaHostAlloc這個函數&#xff0c;它有一些標志&#xff0c;其中cudaHostAllocMapped允許內存映射到設備&#xff0c;也即GPU可以直接訪問主機上的內存&#xff0c;不用額外再給設備指針分配內存 通過下面的操作&#xff0c;即可讓設…

IP地址混亂?監控易IPAM實現全網地址自動化管理與非法接入告警

IP地址出現混亂狀況&#xff1f;監控易IPAM能夠達成對全網地址予以自動化管理的目標&#xff0c;同時還可針對非法接入的情況發出告警信息。辦公室毫無預兆地突然斷網了&#xff0c;經過一番仔細排查之后&#xff0c;發現原來是IP地址出現了沖突的情況。有人私自接了路由器&…

安全監測預警平臺的應用場景

隨著城市化進程加快和基礎設施規模擴大&#xff0c;各類安全風險日益突出。安全監測預警平臺作為現代安全管理的重要工具&#xff0c;通過整合物聯網、大數據、人工智能等先進技術&#xff0c;實現對各類安全隱患的實時監測、智能分析和精準預警。本文將詳細探討安全監測預警平…

007_用例與應用場景

用例與應用場景 目錄 內容創作編程開發數據分析客戶服務教育培訓商業智能研究輔助 內容創作 文案撰寫 應用場景&#xff1a; 營銷文案和廣告語產品描述和說明書社交媒體內容郵件營銷內容 實際案例&#xff1a; 任務&#xff1a;為新款智能手表撰寫產品描述 輸入&#x…

Unity物理系統由淺入深第一節:Unity 物理系統基礎與應用

Unity物理系統由淺入深第一節&#xff1a;Unity 物理系統基礎與應用 Unity物理系統由淺入深第二節&#xff1a;物理系統高級特性與優化 Unity物理系統由淺入深第三節&#xff1a;物理引擎底層原理剖析 Unity物理系統由淺入深第四節&#xff1a;物理約束求解與穩定性 Unity 引擎…

《[系統底層攻堅] 張冬〈大話存儲終極版〉精讀計劃啟動——存儲架構原理深度拆解之旅》-系統性學習筆記(適合小白與IT工作人員)

&#x1f525; 致所有存儲技術探索者筆者近期將系統攻克存儲領域經典巨作——張冬老師編著的《大話存儲終極版》。這部近千頁的存儲系統圣經&#xff0c;以庖丁解牛的方式剖析了&#xff1a;存儲硬件底層架構、分布式存儲核心算法、超融合系統設計哲學等等。喜歡研究數據存儲或…

flutter鴻蒙版 環境配置

flutter支持開發鴻蒙,但是需要專門的flutter鴻蒙項目, Flutter鴻蒙化環境配置&#xff08;windows&#xff09;_flutter config --ohos-sdk-CSDN博客

Java 高級特性實戰:反射與動態代理在 spring 中的核心應用

在 Java 開發中&#xff0c;反射和動態代理常被視為 “高級特性”&#xff0c;它們看似抽象&#xff0c;卻支撐著 Spring、MyBatis 等主流框架的核心功能。本文結合手寫 spring 框架的實踐&#xff0c;從 “原理” 到 “落地”&#xff0c;詳解這兩個特性如何解決實際問題&…

Codeforces Round 855 (Div. 3)

A. Is It a Cat? 去重&#xff0c; 把所有字符看成大寫字符&#xff0c; 然后去重&#xff0c; 觀察最后結果是不是“MEOW” #include <bits/stdc.h> #define int long longvoid solve() {int n;std::cin >> n;std::string ans, t;std::cin >> ans;for (int…

Scrapy選擇器深度指南:CSS與XPath實戰技巧

引言&#xff1a;選擇器在爬蟲中的核心地位在現代爬蟲開發中&#xff0c;??選擇器??是數據提取的靈魂工具。根據2023年網絡爬蟲開發者調查數據顯示&#xff1a;??92%?? 的數據提取錯誤源于選擇器編寫不當熟練使用選擇器的開發效率相比新手提升 ??300%??同時掌握CSS…

Windos服務器升級MySQL版本

Windos服務器升級MySQL版本 1.備份數據庫 windows下必須以管理員身份運行命令行工具進行備份&#xff0c;如果沒有配置MySQL的環境變量&#xff0c;需要進入MySQL Server 的bin目錄輸入指令&#xff0c; mysqldump -u root -p --all-databases > backup.sql再輸入數據庫密碼…