基于SpringBoot实现REST API与RPC调用的统一封装
作者:风象南
一、为何需要统一封装?
在讨论统一封装之前,我们先看看 REST 和 RPC 各自的适用场景。
REST API 基于 HTTP 协议,采用 JSON 作为数据交换格式,可读性好且跨语言,非常适合对外提供服务。
RPC(如 Dubbo、gRPC)采用二进制协议(如 Protobuf),序列化效率高、网络开销小,适合内部微服务间的高频调用。
实际项目中,不同服务可能提供了不同的通信协议,带来服务间调用方式的不一致,带来编码及后续维护的复杂度。
二、设计思路:基于外观模式的统一调用层
解决这个问题的关键是引入 外观模式(Facade Pattern) ,通过一个统一的外观类封装所有调用细节。
同时结合适配器模式和策略模式,实现不同协议的无缝切换。
2.1 核心设计
整个设计分为三层:
统一接口层:定义通用调用接口,屏蔽底层差异 协议适配层:实现 REST 和 RPC 的具体调用逻辑 业务逻辑层:业务服务实现,完全不用关心调用方式
2.2 关键设计模式
外观模式:提供统一入口 UnifiedServiceClient
,封装所有调用细节 适配器模式:将 RestTemplate
和 DubboReference
适配为统一接口 策略模式:根据配置动态选择调用方式(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> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; // 对所有响应生效 } @Override public 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 { @Override public 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>() { @Override public 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; } @Override public <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; } @Override public <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: DISABLE qos-enable: false registry: address: N/A reference: # 为指定服务配置直连地址(无需注册中心) 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 { @Autowired private 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 调用的统一封装。
以上就是基于SpringBoot实现REST API与RPC调用的统一封装的详细内容,更多关于SpringBoot REST API与RPC统一封装的资料请关注脚本之家其它相关文章!