個典型的 Java 泛型在反序列化場景下“類型擦除 + 無法推斷具體類型”導致的隱性 Bug

今天遇到一個問題:一個典型的 Java 泛型在反序列化場景下“類型擦除 + 無法推斷具體類型”導致的隱性 Bug,尤其是在 RPC(如 DubboFeign 等)和 本地 JVM 內直連調用共存時,這種問題會顯現得非常明顯。

A 服務暴露了一個 RPC 接口規范,如下:

public class WeaResult<T> implements Serializable {private static final long serialVersionUID = 15869325700230991L;@ApiModelProperty("狀態碼")private int code;@ApiModelProperty("提示信息")private String msg;@ApiModelProperty("狀態")private boolean status;@ApiModelProperty("數據")private T data;
}

定義的 RPC 接口如下:

WeaResult selectDetail(RuleTypeSettingDto ruleTypeSettingDto);

API 中的返回值沒有聲明泛型 <T> 的具體類型。然后被 B 服務調用了,遠程調用代碼:

private Integer isMultiMode(AllocationRuleDto request) {return Optional.ofNullable(ruleTypeSettingService.selectDetail(RuleTypeSettingDto.builder().moduleName(AllocationComponent.CUSTOMER_SERVICE).typeId(request.getTypeId()).tenantKey(request.getTenantKey()).typeName("cs").build())).map(WeaResult::getData).map(data ->(Map<?,?>)data).map(dataMap -> dataMap.get("sceneType")).map(Object::toString).map(Integer::valueOf).orElse(0);}

接受到結果,只能硬著頭皮強轉,獲取對應值。

這里解釋下,為什么要強轉?

當是 RPC 場景(如 JSON 序列化傳輸)時,框架通常會把 data 轉換為 Map<String, Object>(比如 JSON 默認映射到 HashMap),所以我這里直接強轉成 Map 類型:

map(data -> (Map<?,?>) data)

這樣是能夠能運行的,沒啥問題。

但是,重點來了,當是A 和 B 服務合并單體時部署時(在同一個 JVM 中,或者說是本地部署),就會直接返回原始的具體類型對象(比如是 RuleTypeSettingVo),此時 (Map<?, ?>) data 就會拋 ClassCastException —— 因為根本不是 Map!所以這個就是一個巨坑!這就是沒有合理定義 API 接口導致的,并且泛型一定一定要注明清楚。否則調用方永遠只是一個盲區。

提示:這里的合并指的是將服務提供者和消費者都合并成一個單體服務部署。可能是節省客戶資源。


那么怎么去正確改進呢?
方法一:指定泛型類型,讓接口明確返回結構
WeaResult<RuleTypeSettingVo> selectDetail(RuleTypeSettingDto ruleTypeSettingDto);

這樣無論是遠程調用還是本地調用,返回值類型一致,調用方可以安全地 (Map),但是不推薦用 RuleTypeSettingVo 還是,大部分都是按照實體返回。所以,定義 API 規范時,一定要明確所有出入參,以及涉及到的泛型。

另外,定義了這種 WeaResultcode + status 返回的,一定要優先判斷 code + status。否則,你一定會吃大虧,code + status 可以讓我們在調用遠程接口時減少很多不必要的麻煩

方法二:在調用方顯式判斷類型(不推薦)

如果你不能修改接口,但調用方需要容錯處理,可以使用:

Object data = ruleTypeSettingService.selectDetail(...).getData();
Map<?, ?> dataMap;
if (data instanceof Map) {dataMap = (Map<?, ?>) data;
} else {// 使用 BeanUtils 或反射將對象轉換為 MapdataMap = convertBeanToMap(data);
}

或者

data -> JSONObject.parseObject(JSON.toJSONString(data), Map.class))

你可以封裝一個 convertBeanToMap(Object obj) 工具類,比如用 Apache Commons BeanUtils、Spring 的 BeanWrapperImpl 或自定義反射實現。

但是這種方法不推薦這樣做,對調用方太不友好,而且寫這樣的代碼很不好維護。這只是一個臨時解決方案!

建議:為 RPC 接口統一泛型類型!!!

應該避免接口返回 WeaResult 沒有明確泛型,否則不同的調用方(遠程 vs 本地)會得到結構不一致的對象,嚴重時導致生產級兼容問題

建議的統一寫法:

WeaResult<Map<String, Object>> selectDetail(RuleTypeSettingDto ruleTypeSettingDto);

或者如果你能保證返回值是某個固定 VO 類:

WeaResult<RuleTypeSettingVo> selectDetail(RuleTypeSettingDto ruleTypeSettingDto);

然后在調用方處理:

RuleTypeSettingVo vo = result.getData();
vo.getSceneType(); // 等價于 map.get("sceneType")
最后推薦大家:

RPC 接口的返回值類型一旦模糊(如未指定泛型),不管是微服務架構體系,還是合并單體公用同一個 JVM,使用時都可能導致結果不一致,最穩妥做法是*統一泛型類型(推薦)或封裝類型轉換邏輯(不推薦)。

推薦閱讀文章

  • 由 Spring 靜態注入引發的一個線上T0級別事故(真的以后得避坑)

  • 如何理解 HTTP 是無狀態的,以及它與 Cookie 和 Session 之間的聯系

  • HTTP、HTTPS、Cookie 和 Session 之間的關系

  • 什么是 Cookie?簡單介紹與使用方法

  • 什么是 Session?如何應用?

  • 使用 Spring 框架構建 MVC 應用程序:初學者教程

  • 有缺陷的 Java 代碼:Java 開發人員最常犯的 10 大錯誤

  • 如何理解應用 Java 多線程與并發編程?

  • 把握Java泛型的藝術:協變、逆變與不可變性一網打盡

  • Java Spring 中常用的 @PostConstruct 注解使用總結

  • 如何理解線程安全這個概念?

  • 理解 Java 橋接方法

  • Spring 整合嵌入式 Tomcat 容器

  • Tomcat 如何加載 SpringMVC 組件

  • “在什么情況下類需要實現 Serializable,什么情況下又不需要(一)?”

  • “避免序列化災難:掌握實現 Serializable 的真相!(二)”

  • 如何自定義一個自己的 Spring Boot Starter 組件(從入門到實踐)

  • 解密 Redis:如何通過 IO 多路復用征服高并發挑戰!

  • 線程 vs 虛擬線程:深入理解及區別

  • 深度解讀 JDK 8、JDK 11、JDK 17 和 JDK 21 的區別

  • 10大程序員提升代碼優雅度的必殺技,瞬間讓你成為團隊寵兒!

  • “打破重復代碼的魔咒:使用 Function 接口在 Java 8 中實現優雅重構!”

  • Java 中消除 If-else 技巧總結

  • 線程池的核心參數配置(僅供參考)

  • 【人工智能】聊聊Transformer,深度學習的一股清流(13)

  • Java 枚舉的幾個常用技巧,你可以試著用用

  • 由 Spring 靜態注入引發的一個線上T0級別事故(真的以后得避坑)

  • 如何理解 HTTP 是無狀態的,以及它與 Cookie 和 Session 之間的聯系

  • HTTP、HTTPS、Cookie 和 Session 之間的關系

  • 使用 Spring 框架構建 MVC 應用程序:初學者教程

  • 有缺陷的 Java 代碼:Java 開發人員最常犯的 10 大錯誤

  • Java Spring 中常用的 @PostConstruct 注解使用總結

  • 線程 vs 虛擬線程:深入理解及區別

  • 深度解讀 JDK 8、JDK 11、JDK 17 和 JDK 21 的區別

  • 10大程序員提升代碼優雅度的必殺技,瞬間讓你成為團隊寵兒!

  • 探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)

  • 為什么用了 @Builder 反而報錯?深入理解 Lombok 的“暗坑”與解決方案(二)

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

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

相關文章

開發指南121-微服務的彈性伸縮

平臺的后臺服務表現形式就是各種各樣的微服務。微服務可以部署在不同的機器上。單一服務的伸縮很簡單&#xff1a; 部署在不同機器上&#xff0c;直接啟動關閉即可。 部署在同一機器上&#xff0c;可以復制為多個不同目錄&#xff0c;其中jar包&#xff0c;啟動文件是完全一樣…

【C++特殊工具與技術】優化內存分配(六):運行時類型識別

目錄 一、RTTI 的核心機制與設計背景 1.1 RTTI 的設計目標 1.2 RTTI 的啟動條件 二、dynamic_cast&#xff1a;動態類型轉換 2.1 語法與核心特性 2.2 轉換場景詳解 2.3 引用類型轉換與異常處理 2.4 性能注意事項 三、typeid&#xff1a;類型信息查詢 3.1 語法與核心特…

USB串口通信、握手協議、深度學習等技術要點

基于OpenMV的智能車牌識別系統&#xff1a;從硬件到算法的完整實現 前言 本文將詳細介紹一個基于OpenMV微控制器的智能車牌識別系統的設計與實現。該系統集成了嵌入式視覺處理、串口通信協議、深度學習OCR識別等多種技術&#xff0c;實現了從圖像采集到車牌識別的完整流程。 …

獵板PCB:手機主板pcb需要做哪些可靠性測試

在智能手機高度普及的今天&#xff0c;一塊指甲蓋大小的主板承載著通信、計算、影像等核心功能。當消費者為新機性能歡呼時&#xff0c;鮮少有人關注到主板PCB&#xff08;印刷電路板&#xff09;在幕后經歷的嚴苛考驗。這些隱藏在金屬外殼下的精密線路&#xff0c;需要經過多輪…

Java并發編程實戰 Day 21:分布式并發控制

【Java并發編程實戰 Day 21】分布式并發控制 文章簡述&#xff1a; 在高并發和分布式系統中&#xff0c;傳統的線程級鎖已無法滿足跨節點的同步需求。本文深入講解了分布式并發控制的核心概念與技術方案&#xff0c;包括分布式鎖、一致性算法&#xff08;如Paxos、Raft&#x…

C語言文件操作與預處理詳解

目錄 文件操作文件基本概念文件指針文件打開模式文件讀取操作字符讀取字符串讀取格式化讀取二進制讀取 文件寫入操作字符寫入字符串寫入格式化寫入二進制寫入 文件定位操作文件錯誤處理 預處理預處理基本概念常見預處理指令文件包含指令宏定義簡單宏帶參數的宏字符串化操作符(#…

水庫大壩安全監測之滲流監測

水庫大壩的滲流狀況直接關系到其結構穩定性與安全運行。滲流可能引發壩體內部土體的滲透變形&#xff0c;如管涌、流土等現象&#xff0c;削弱壩體強度&#xff0c;嚴重時甚至導致大壩垮塌&#xff0c;威脅下游人民生命財產安全。通過滲流監測&#xff0c;能夠實時掌握壩體及壩…

windows使用命令行查看進程信息

在 Windows 操作系統中&#xff0c;您可以使用多種命令行工具來查看進程信息。以下是幾種常用方法&#xff1a; 1. 使用 tasklist 命令&#xff08;最常用&#xff09; 查看所有進程的基本信息&#xff1a; tasklist輸出示例&#xff1a; 映像名稱 PID…

【C#】多級緩存與多核CPU

多級緩存&#xff08;如CPU的L1/L2/L3緩存&#xff09;與多核處理器之間存在緊密的協同與競爭關系&#xff0c;直接影響系統性能。以下是關鍵影響及優化策略&#xff1a; 一、緩存層級與多核的協作機制 緩存結構 L1緩存 私有緩存&#xff1a;每個CPU核心獨享&#xff0c;容量小…

PostgreSQL的擴展adminpack

PostgreSQL的擴展adminpack adminpack 是 PostgreSQL 提供的一個管理擴展&#xff0c;它包含多個實用函數&#xff0c;幫助數據庫管理員執行文件系統操作和維護任務。這個擴展通常由數據庫超級用戶使用&#xff0c;提供了一些服務器端的文件訪問功能。 一、adminpack 擴展概述…

Unity | AmplifyShaderEditor插件基礎(第九集:旗子進階版)

目錄 一、&#x1f44b;&#x1f3fb;前言 二、準備工作 1.下載安裝插件ProBuilder 2.下載安裝插件Polybrush 3.固定原理 4.旗子 三、頂點上色 1.創建一個可以頂點上色的材質 2.開始上色 a.上色功能說明 b.全部上色 c.調整刷子 四、shader的設置 1.幅度添加 2.頂…

Java 實現 Excel 轉化為 PDF

引言 在實際開發中&#xff0c;將 Excel 文件轉化為 PDF 格式是一項常見需求。例如在需要共享數據報表時&#xff0c;PDF 格式具有更好的兼容性和安全性。GrapeCity Documents for Excel&#xff08;GcExcel&#xff09;為 Java 開發者提供了強大的工具&#xff0c;可輕松實現…

Spring Boot3批式訪問Dify聊天助手接口

Spring Boot3批式訪問Dify聊天助手接口 前言 之前已經配置好Dify1.4.1及LM Studio集成&#xff1a; https://lizhiyong.blog.csdn.net/article/details/148607462 現在就可以借助Spring Boot3去訪問Dify的后端接口&#xff0c;讓前端展示大模型的返回內容。這是我等大數據資…

事務傳播行為詳解

一、事務傳播行為的基本概念 事務傳播行為是Spring 框架中事務管理的核心概念&#xff0c;用于定義當一個事務方法被另一個事務方法調用時&#xff0c;事務應如何傳播。通俗地說&#xff0c;它解決了 “多個事務方法嵌套調用時&#xff0c;新方法是加入現有事務還是創建新事務…

Java八股文——Spring「SpringMVC 篇」

MVC分層介紹一下 面試官您好&#xff0c;MVC是一種非常經典、影響深遠的軟件設計模式&#xff0c;它的全稱是Model-View-Controller。在我看來&#xff0c;它的核心目標就是解決早期Web開發中&#xff0c;業務邏輯、數據和界面顯示高度耦合的問題&#xff0c;從而實現“各司其…

FreeSWITCH mod_curl 和 mod_xml_rpc 測試

編輯 /usr/local/freeswitch/conf/autoload_configs/xml_rpc.conf.xml <configuration name"xml_rpc.conf" description"XML RPC"> <settings> <param name"http-port" value"8889"/> <param name&quo…

實時監控、秒級決策:鏡舟科技如何重塑融資融券業務數據處理模式

融資融券業務作為證券市場的重要組成部分&#xff0c;已成為金融機構核心業務增長點和利潤來源。截至 2023 年底&#xff0c;我國融資融券余額已突破 1.8 萬億元&#xff0c;業務量呈現爆發式增長。然而&#xff0c;在業務高速發展的同時&#xff0c;金融機構面臨著數據處理效率…

Linux與量子計算:面向未來的架構演進

Linux與量子計算&#xff1a;面向未來的架構演進 當經典計算遇上量子革命 引言&#xff1a;量子計算時代的黎明 量子計算正從理論走向工程實踐&#xff0c;Linux作為現代計算的基石&#xff0c;正在量子革命中扮演關鍵角色。據IBM預測&#xff0c;到2027年&#xff0c;量子優勢…

Java中wait()為何必須同步調用?

在 Java 中&#xff0c;wait() 方法必須在 synchronized 方法或代碼塊中調用&#xff0c;主要原因如下&#xff1a; 1. 監視器鎖&#xff08;Monitor&#xff09;機制 依賴對象鎖&#xff1a;wait() 方法需要操作對象的監視器鎖&#xff08;Monitor&#xff09;&#xff0c;調…

前端面試專欄-基礎篇:4. 頁面渲染流程與性能優化

頁面渲染流程與性能優化詳解&#xff08;完整版&#xff09; 一、現代瀏覽器渲染流程&#xff08;詳細說明&#xff09; 1. 構建DOM樹 瀏覽器接收到HTML文檔后&#xff0c;會逐步解析并構建DOM&#xff08;Document Object Model&#xff09;樹。具體過程如下&#xff1a; (…