一個 List.of 引發的“血案”
來源:阿里雲開發者
阿里妹導讀
本文作者將分享一個使用List.of後掉進的坑以及爬坑的全過程,希望大家能引以為戒同時引起這樣的意識:在使用新技術前先搞清楚其實現的原理。
引
案發現場
一句話總結:在一次後端釋出的變更後,前端解析介面返回的格式失敗。
前情提要:
後端 JAVA 應用 JDK 版本11,提供 HSF 服務端介面。
前端透過陸游平臺(一個 Node 視覺化邏輯編排的平臺)配置介面,內部透過 node 泛化呼叫後端的 HSF 介面,平臺解析返回介面結果。
後端釋出的變更示意:
// 釋出前
public List<String> before(Long id) {
...
if (...) {
return null;
}
...
}
// 釋出後
public List<String> after(Long id) {
...
if (...) {
return List.of();
}
...
}
這裡的核心變化點就是將預設的返回從 null 改成了 List.of() 。
為什麼可以這麼改?已知前端對null和空陣列[]做了同樣的相容邏輯。
前端獲取到介面的格式變化:
// 釋出前{ "test": null}// 釋出後{ "test": { "tag": 1 }}
案情推理
1. 初窺門徑:List.of
public interface List<E> extends Collection<E> { /** * Returns an unmodifiable list containing zero elements. * * See <a href="#unmodifiable">Unmodifiable Lists</a> for details. * * @param <E> the {@code List}'s element type * @return an empty {@code List} * * @since 9 */ static <E> List<E> of() { return ImmutableCollections.emptyList(); }}
從官方註釋中得到3點結論:
這是一個 JDK9 之後的特性; 返回的是一個不可修改的陣列; 底層實現使用的 ImmutableCollections 的 emptyList 方法,而 ImmutableCollections 這個類是一個不可變集合的容器類;
2. 漸入佳境:ImmutableCollections.emptyList
class ImmutableCollections {
static <E> List<E> emptyList() {
return (List<E>) ListN.EMPTY_LIST;
}
static final class ListN<E> extends AbstractImmutableList<E>
implements Serializable {
// EMPTY_LIST may be initialized from the CDS archive.
static @Stable List > EMPTY_LIST;
static {
VM.initializeFromArchive(ListN.class);
if (EMPTY_LIST == null) {
EMPTY_LIST = new ListN<>();
}
}
...
}
static abstract class AbstractImmutableList<E> extends AbstractImmutableCollection<E>
implements List<E>, RandomAccess {
...
}
}
到這一步,案件的主人公終於登場了:一個新的類 ListN。但是在這段程式碼中,還有很多隱藏的細節線索:
ListN 是 List 的實現類:ListN 繼承了AbstractImmutableList,而 AbstractImmutableList 實際又實現了List; ListN 中的靜態變數 EMPTY_LIST 會被初始化為一個空的 ListN 的物件; emptyList 方法中做了 List 型別的強轉,但是由於JAVA的型別轉換原則,實際仍然返回的是一個ListN物件(這是關鍵線索之一),透過排查過程中發現的阿爾薩斯監控也可以確認這一點:
3. 直擊要害:node的 HSF 解析
一個完整的型別對映表可以檢視:java-物件與-node-的對應關係以及呼叫方法
而遇到這次返回的 ListN,可以確定是這種特殊型別在序列化/反序列化的過程中出現了不同的邏輯導致。
4. 真相大白:ListN的序列化
static final class ListN<E> extends AbstractImmutableList<E>
implements Serializable {
private final E[] elements;
ListN(E... input) {
// copy and check manually to avoid TOCTOU
"unchecked") (
E[] tmp = (E[])new Object[input.length]; // implicit nullcheck of input
for (int i = 0; i < input.length; i++) {
tmp[i] = Objects.requireNonNull(input[i]);
}
elements = tmp;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
throw new InvalidObjectException("not serial proxy");
}
private Object writeReplace() {
return new CollSer(CollSer.IMM_LIST, elements);
}
}
CollSer.IMM_LIST 靜態值 = 1
elements 一個空的物件陣列 = new Object[0]
final class CollSer implements Serializable {
private static final long serialVersionUID = 6309168927139932177L;
static final int IMM_LIST = 1;
static final int IMM_SET = 2;
static final int IMM_MAP = 3;
private final int tag;
/**
* @serial
* @since 9
*/
private transient Object[] array;
CollSer(int t, Object... a) {
tag = t;
array = a;
}
}
tag = 1
是怎麼來的了。結案陳詞
List.of()
做返回,在 node 呼叫 HSF 序列化獲取返回結果時會解析成一個帶有tag欄位的物件,而不是預期的空陣列。這個問題其實想解決很簡單,將 List.of()
替換成我們常用的 Lists.newArrayList()
就行,本質上還是對底層實現的不清晰不瞭解導致了這整個事件。當然在結尾處,其實還有一個疑點,在 HSF 控制檯除錯這個介面的時候,我發現它的 json 結構是可以正確解析的:
懷疑可能是序列化型別的問題,hsfops 也是用了泛化呼叫,序列化型別是 hessian,可能 node 的序列化型別不一樣,這個後續研究確定後我再補充一下。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2994884/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 一個map函式引發的血案函式
- 一個UPDATE語句引發的血案
- 一個ES設定操作引發的“血案”
- 實戰|一個表白牆引發的“血案”
- 一個系統BUG引發的血案 -- FKDownloader
- 一個 Handler 面試題引發的血案!!!面試題
- MySQL 中一個雙引號的錯位引發的血案MySql
- 一個由line-height引發的血案與思考
- 事故現場:MySQL 中一個雙引號的錯位引發的血案MySql
- 為什麼redux要返回一個新的state引發的血案Redux
- 一場 Kafka CRC 異常引發的血案Kafka
- 一道面試題引發的“血案”面試題
- 一場由postcss-bem引發的血案CSS
- 研發效能度量引發的血案
- 由Ghost漏洞引發的“血案”
- RestTemplate超時引發的血案REST
- JDBC亂碼引發的"血案"JDBC
- 為什麼 redux 要返回一個新的 state 引發的血案(二)Redux
- 記一次Content-Length引發的血案
- 【原創】經驗分享:一個Content-Length引發的血案(almost....)
- Flutter 中由 BuildContext 引發的血案FlutterUIContext
- vue watch陣列引發的血案Vue陣列
- async,await與forEach引發的血案AI
- 控制檔案不一致引發的“血案”
- io.Reader遊標引發的血案
- Maven依賴版本號引發的血案Maven
- 一場版本升級引發的效能血案的追凶過程
- js正則全域性匹配引發的血案JS
- 一場由AI引發的GPU血案,AMD還有機會嗎?AIGPU
- 斷點除錯之壓縮引發的血案斷點除錯
- 程式設計師跟產品經理打起來了,這是一個需求引發的血案...程式設計師
- 一行程式碼引發的”血案“!!!(軟體開發、專案管理、skycto JEEditor)行程專案管理
- 做面試的不倒翁:一道事件迴圈題引發的血案面試事件
- git merge使用不當引發的程式碼丟失血案Git
- 一行超長日誌引發的 “血案” - Containerd 頻繁 OOM 背後的真相AIOOM
- 一個commit引發的思考MIT
- 一個排序引發的BUG排序
- 由一個emoji引發的思考