最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于SpringBoot實(shí)現(xiàn)REST?API與RPC調(diào)用的統(tǒng)一封裝

 更新時(shí)間:2025年07月14日 08:24:28   作者:風(fēng)象南  
REST?API?基于?HTTP?協(xié)議,采用?JSON?作為數(shù)據(jù)交換格式,可讀性好且跨語(yǔ)言,非常適合對(duì)外提供服務(wù),RPC采用二進(jìn)制協(xié)議,序列化效率高、網(wǎng)絡(luò)開銷小,實(shí)際項(xiàng)目中,不同服務(wù)可能提供了不同的通信協(xié)議,所以本文給大家介紹了如何基于SpringBoot實(shí)現(xiàn)REST?API與RPC調(diào)用的統(tǒng)一封裝

一、為何需要統(tǒng)一封裝?

在討論統(tǒng)一封裝之前,我們先看看 REST 和 RPC 各自的適用場(chǎng)景。

REST API 基于 HTTP 協(xié)議,采用 JSON 作為數(shù)據(jù)交換格式,可讀性好且跨語(yǔ)言,非常適合對(duì)外提供服務(wù)。

RPC(如 Dubbo、gRPC)采用二進(jìn)制協(xié)議(如 Protobuf),序列化效率高、網(wǎng)絡(luò)開銷小,適合內(nèi)部微服務(wù)間的高頻調(diào)用。

實(shí)際項(xiàng)目中,不同服務(wù)可能提供了不同的通信協(xié)議,帶來(lái)服務(wù)間調(diào)用方式的不一致,帶來(lái)編碼及后續(xù)維護(hù)的復(fù)雜度。

二、設(shè)計(jì)思路:基于外觀模式的統(tǒng)一調(diào)用層

解決這個(gè)問(wèn)題的關(guān)鍵是引入 外觀模式(Facade Pattern) ,通過(guò)一個(gè)統(tǒng)一的外觀類封裝所有調(diào)用細(xì)節(jié)。

同時(shí)結(jié)合適配器模式和策略模式,實(shí)現(xiàn)不同協(xié)議的無(wú)縫切換。

2.1 核心設(shè)計(jì)

整個(gè)設(shè)計(jì)分為三層:

統(tǒng)一接口層:定義通用調(diào)用接口,屏蔽底層差異 協(xié)議適配層:實(shí)現(xiàn) REST 和 RPC 的具體調(diào)用邏輯 業(yè)務(wù)邏輯層:業(yè)務(wù)服務(wù)實(shí)現(xiàn),完全不用關(guān)心調(diào)用方式

2.2 關(guān)鍵設(shè)計(jì)模式

外觀模式:提供統(tǒng)一入口 UnifiedServiceClient,封裝所有調(diào)用細(xì)節(jié) 適配器模式:將 RestTemplateDubboReference 適配為統(tǒng)一接口 策略模式:根據(jù)配置動(dòng)態(tài)選擇調(diào)用方式(REST 或 RPC)

三、實(shí)現(xiàn)步驟:從統(tǒng)一響應(yīng)到協(xié)議適配

3.1 統(tǒng)一響應(yīng)體設(shè)計(jì)

首先要解決的是返回格式不一致問(wèn)題。我們定義了統(tǒng)一的響應(yīng)體 ApiResponse

@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResponse<T> implements Serializable {
    private String code;       // 狀態(tài)碼
    private String message;    // 消息提示
    private T data;            // 業(yè)務(wù)數(shù)據(jù)
    private long timestamp;    // 時(shí)間戳

    // 成功響應(yīng)
    public static <T> ApiResponse<T> success(T data) {
        return ApiResponse.<T>builder()
                .code("200")
                .message("success")
                .data(data)
                .timestamp(System.currentTimeMillis())
                .build();
    }

    // 失敗響應(yīng)
    public static <T> ApiResponse<T> fail(String code, String message) {
        return ApiResponse.<T>builder()
                .code(code)
                .message(message)
                .timestamp(System.currentTimeMillis())
                .build();
    }
}

對(duì)于 REST 接口,通過(guò) @RestControllerAdvice 實(shí)現(xiàn)自動(dòng)封裝

@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true; // 對(duì)所有響應(yīng)生效
    }

    @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); // 未封裝的自動(dòng)包裝
    }
}

3.2 統(tǒng)一異常處理

異常處理同樣需要統(tǒng)一。對(duì)于 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("系統(tǒng)異常", e);
        return ApiResponse.fail("500", "系統(tǒng)內(nèi)部錯(cuò)誤");
    }
}

對(duì)于 Dubbo RPC,通過(guò)自定義過(guò)濾器實(shí)現(xiàn)異常轉(zhuǎn)換:

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調(diào)用異常"));
        }
    }
}

3.3 協(xié)議適配層實(shí)現(xiàn)

定義統(tǒng)一調(diào)用接口 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);
}

然后分別實(shí)現(xiàn) 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");
        // 從配置文件讀取直連地址(優(yōu)先級(jí):代碼 > 配置文件)
        String directUrl = environment.getProperty("dubbo.reference." + serviceName + ".url");
        if (StrUtil.isNotEmpty(directUrl)) {
            reference.setUrl(directUrl);  // 設(shè)置直連地址,覆蓋注冊(cè)中心發(fā)現(xiàn)
        }

        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 外觀類與策略選擇

最后實(shí)現(xiàn)外觀類 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) {
        // 根據(jù)配置選擇調(diào)用方式
        String protocol = serviceConfig.getProtocol(serviceName);
        ServiceInvoker invoker = protocol.equals("rpc") ? 
            invokerMap.get("DubboServiceInvoker") : 
            invokerMap.get("RestServiceInvoker");
        return invoker.invoke(serviceName, method, param,resultType);
    }
}

服務(wù)調(diào)用方式通過(guò)配置文件指定:

service:
  direct-url: # 直連地址配置
    user-service: http://localhost:8080/user  # 訂單服務(wù)REST地址
  config:
    user-service: rest    # 用戶服務(wù)用rest調(diào)用
    order-service: rpc  # 訂單服務(wù)用RPC調(diào)用

# Dubbo 配置(若使用 Dubbo RPC)
dubbo:
  application:
    name: unified-client-demo  # 當(dāng)前應(yīng)用名
#    serialize-check-status: DISABLE
    qos-enable: false
  registry:
    address: N/A
  reference:
    # 為指定服務(wù)配置直連地址(無(wú)需注冊(cè)中心)
    order-service:
      interfaceName: com.example.unified.service.OrderService  # 服務(wù)接口名稱
      url: dubbo://192.168.17.1:20880  # 格式:dubbo://IP:端口
  protocol:
    name: dubbo    # RPC 協(xié)議名稱
    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;
    }
}

五、總結(jié)

通過(guò)外觀模式 + 適配器模式 + 策略模式的組合,實(shí)現(xiàn)了 REST API 與 RPC 調(diào)用的統(tǒng)一封裝。

以上就是基于SpringBoot實(shí)現(xiàn)REST API與RPC調(diào)用的統(tǒng)一封裝的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot REST API與RPC統(tǒng)一封裝的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 淺談 JDBC 元數(shù)據(jù)

    淺談 JDBC 元數(shù)據(jù)

    這篇文章主要介紹了JDBC元數(shù)據(jù)的相關(guān)內(nèi)容,涉及一些獲取數(shù)據(jù)源各種信息的方法,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-09-09
  • nacos使用占位符${}進(jìn)行參數(shù)配置的方法

    nacos使用占位符${}進(jìn)行參數(shù)配置的方法

    這篇文章主要介紹了nacos如何使用占位符${}進(jìn)行參數(shù)配置,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-12-12
  • SpringBoot整合Nacos注冊(cè)中心的實(shí)現(xiàn)示例

    SpringBoot整合Nacos注冊(cè)中心的實(shí)現(xiàn)示例

    本文通過(guò)Nacos實(shí)現(xiàn)SpringBoot與SpringCloudGateway的配置熱更新、共享配置及動(dòng)態(tài)路由,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-07-07
  • 基于@RequestParam name和value屬性的區(qū)別

    基于@RequestParam name和value屬性的區(qū)別

    這篇文章主要介紹了@RequestParam name和value屬性的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Eureka注冊(cè)不上或注冊(cè)后IP不對(duì)(多網(wǎng)卡的坑及解決)

    Eureka注冊(cè)不上或注冊(cè)后IP不對(duì)(多網(wǎng)卡的坑及解決)

    這篇文章主要介紹了Eureka注冊(cè)不上或注冊(cè)后IP不對(duì)(多網(wǎng)卡的坑及解決),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • JAVA中Synchronized能否加鎖字符串詳解

    JAVA中Synchronized能否加鎖字符串詳解

    在Java中Synchronized可以鎖任何對(duì)象,包括字符串,但由于字符串常量在字符串池中是共享的,所以如果多個(gè)線程使用相同的字符串作為鎖對(duì)象,可能會(huì)導(dǎo)致意外的行為,這篇文章主要介紹了JAVA中Synchronized能否加鎖字符串的相關(guān)資料,需要的朋友可以參考下
    2025-07-07
  • SpringBoot如何解析參數(shù)的深入理解

    SpringBoot如何解析參數(shù)的深入理解

    這篇文章主要給大家介紹了關(guān)于SpringBoot是如何解析參數(shù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用SpringBoot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • SpringBoot集成EasyExcel實(shí)現(xiàn)Excel導(dǎo)入的方法

    SpringBoot集成EasyExcel實(shí)現(xiàn)Excel導(dǎo)入的方法

    這篇文章主要介紹了SpringBoot集成EasyExcel實(shí)現(xiàn)Excel導(dǎo)入的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • Java最長(zhǎng)公共子序列示例源碼

    Java最長(zhǎng)公共子序列示例源碼

    這篇文章主要介紹了Java最長(zhǎng)公共子序列的定義及示例源代碼,具有一定參考價(jià)值,需要的朋友可以看下。
    2017-09-09
  • 如何使用Jenkins構(gòu)建GIT+Maven項(xiàng)目

    如何使用Jenkins構(gòu)建GIT+Maven項(xiàng)目

    這篇文章主要介紹了如何使用Jenkins構(gòu)建GIT+Maven項(xiàng)目,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09

最新評(píng)論

宿迁市| 扎赉特旗| 阿坝县| 新闻| 鄯善县| 澎湖县| 会宁县| 旬阳县| 浠水县| 上高县| 桑日县| 峡江县| 商水县| 石首市| 嘉荫县| 宿松县| 孙吴县| 镇赉县| 淳化县| 江孜县| 鹿泉市| 新化县| 明光市| 兴山县| 揭西县| 鸡东县| 高要市| 湘潭县| 江阴市| 泰兴市| 宜兴市| 牡丹江市| 榆社县| 威远县| 乐昌市| 马公市| 衡阳县| 大名县| 当阳市| 陆良县| 久治县|