基于 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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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再输入数据库密码…