首页 > 应用开发 > Java > 一个 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点结论:
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。但是在这段代码中,还有很多隐藏的细节线索:
emptyList 方法中做了 List 类型的强转,但是由于JAVA的类型转换原则,实际仍然返回的是一个ListN对象(这是关键线索之一),通过排查过程中发现的阿尔萨斯监控也可以确认这一点:

3. 直击要害:node的 HSF 解析

一个完整的类型映射表可以查看:java-对象与-node-的对应关系以及调用方法
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 inputfor (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博客 ” ,链接:https://blog.itpub.net/70024923/viewspace-2994884/ ,如需转载,请注明出处,否则将追究法律责任。