尝试使用 Java 9 或更高版本反序列化特殊对象图时出现 NullPointerException

问题描述 投票:0回答:1
我(作为团队的一员)正在开发一个企业 Java 应用程序,该应用程序已经积极开发了 15 年以上。我们希望构建一个 REST API 来调用“核心”应用程序的一些 EJB。目前“核心”正在 Java 8 (Oracle) 上运行,我们希望使用 Java 21 或 Java 11 来开发 REST API。

在处理 POC 时,我们注意到特定 EJB 调用的返回值无法由较新的 Java 版本反序列化。

我已经成功创建了一个简化的重现器,其中仅包含尽可能简化的重现问题所需的类。可以在

在这里找到该复制器。

如果按照重现器中的方式构造特定对象图,并且当 Airport 对象是根时将其序列化为文件,则反序列化会失败并出现以下 NPE:

java.lang.NullPointerException at edu.gozke.BaseCodeObject.hashCode(BaseCodeObject.java:57) at java.base/java.util.HashMap.hash(HashMap.java:340) at java.base/java.util.HashMap.put(HashMap.java:608) at java.base/java.util.HashSet.readObject(HashSet.java:343) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at java.base/java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1046) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2357) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2228) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1687) at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2496) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2390) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2228) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1687) at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2496) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2390) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2228) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1687) at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2496) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2390) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2228) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1687) at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:489) at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:447) at edu.gozke.SerializationTest.readObject(SerializationTest.java:47) at edu.gozke.SerializationTest.deserializationFailsIfAirportIsTheRoot(SerializationTest.java:26)
相同的代码在 Java 8 上运行良好(不会抛出 NPE)。从 java 9 开始,出现上述错误。请参阅链接的存储库以了解其他尝试过的 JDK 版本。

我对这个问题的疑问是:

    为什么反序列化在较新版本的 JDK 中工作方式不同?
  • 我应该在哪个论坛上查询更多信息?
编辑:按照建议,我将包含以下来源。 (现在没有原始类型和无用的

assert

语句)

public class City extends BaseCodeObject { private final Set<Airport> airports = new HashSet<>(); City(String iataCityCode) { super(iataCityCode); } public void addAirport(Airport airport) { airports.add(airport); } } public abstract class BaseCodeObject implements Serializable, Comparable<BaseCodeObject> { protected String code; protected int hash; protected BaseCodeObject(String code) { assert (code != null); this.code = code; } public final String getCode() { return code; } @Override public int compareTo(BaseCodeObject obj) { return code.compareTo(obj.code); } public boolean equals(Object otherObject) { if (otherObject == this) { return true; } if (otherObject == null) { return false; } if (!(otherObject instanceof BaseCodeObject)) { return false; } BaseCodeObject otherBaseObject = (BaseCodeObject) otherObject; return code.equals(otherBaseObject.code); } @Override public int hashCode() { if (hash == 0) { hash = code.hashCode(); } return hash; } } public class Airport extends CodedBaseDataContainer { Airport(String iataAirportCode) { super(iataAirportCode); } void addAirportData(AirportData data) { addBaseData(data); } } public abstract class CodedBaseDataContainer extends BaseCodeObject { private final static PeriodComparator COMPARATOR = new PeriodComparator(); protected final TreeMap<String, CodedPeriodBaseData> dataMap = new TreeMap<>(COMPARATOR); protected CodedBaseDataContainer(String code) { super(code); } protected final void addBaseData(CodedPeriodBaseData baseData) { dataMap.put(baseData.getEffectivePeriod(), baseData); } private static class PeriodComparator implements Comparator<String>, Serializable { public int compare(String period1, String period2) { return period1.compareTo(period2); } } } public class AirportData extends CodedPeriodBaseData { private final City city; AirportData(Airport airport, City city, String effectivePeriod) { super(airport, effectivePeriod); this.city = city; airport.addAirportData(this); } } public abstract class CodedPeriodBaseData extends BaseCodeObject { private final String effectivePeriod; protected final CodedBaseDataContainer container; protected CodedPeriodBaseData(CodedBaseDataContainer container, String effectivePeriod) { super(container.getCode()); this.container = container; this.effectivePeriod = effectivePeriod; } public String getEffectivePeriod() { return effectivePeriod; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o == null) { return false; } if (!(o instanceof CodedPeriodBaseData)) { return false; } CodedPeriodBaseData data = (CodedPeriodBaseData) o; return code.equals(data.code) && effectivePeriod.equals(data.effectivePeriod); } @Override public int hashCode() { if (hash == 0) { hash = 17; hash = 37 * hash + code.hashCode(); hash = 37 * hash + effectivePeriod.hashCode(); } return hash; } @Override public int compareTo(BaseCodeObject obj) { CodedPeriodBaseData data = (CodedPeriodBaseData) obj; int cmpCode = code.compareTo(data.code); if (cmpCode != 0) { return cmpCode; } return effectivePeriod.compareTo(data.effectivePeriod); } }
还有有问题的重现方法:

public void deserializationFailsIfAirportIsTheRoot() throws ClassNotFoundException, IOException { City city = new City("BB"); Airport airport = new Airport("BB"); city.addAirport(airport); new AirportData(airport, city, "dummyKey"); writeToFile(airport, "airport"); Object object = readObject("airport"); System.out.println(object.hashCode()); } private static final Object readObject(String fileName) throws IOException, ClassNotFoundException { try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) { return ois.readObject(); } } private static void writeToFile(Object obj, String fileName) throws FileNotFoundException, IOException { try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) { oos.writeObject(obj); } }
    
java deserialization hashcode
1个回答
0
投票
因此,基于 42 的

answer,我开始探索另一个 StackOverflow 问题,它让我陷入了困境,在 openjdk 存储库中找到了引入此行为的提交。

此提交

就在这里是引入相关更改的地方。从相关JBS票据的描述来看,作者并没有意识到这种副作用。 我找不到任何预构建的二进制文件来证明此提交“破坏”了反序列化,因此我决定在本地构建两个版本。一个包含中断提交 (e11aec59a2705854ded7eaf2103d9cde57d81652)

 (A),另一个来自该提交之前的版本 
(a1e2230a4045afb7930f95dca6a351339403f41d)
 (B)。
重现器在构建 A 时抛出 NPE,但在构建 B 时工作正常。

在研究过程中,我的队友发现这个问题已经在 JBS 中至少报告过两次,但有一段时间没有被提及。这些问题是:

  • JDK-8201131
  • JDK-8199664
基于 JDK-8199664 中提到的解决方法,如果我使用以下方法扩展

BaseCodeObject

 类,则反序列化工作正常。

private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); }
这样我原来的问题就得到了解答。

© www.soinside.com 2019 - 2024. All rights reserved.