我正在(作为团队的一员)开发一个Enterprise Java应用程序,该应用程序已经活跃开发了15年以上.我们希望构建一个REST API,它可以调用"核心"应用程序的一些EJB.目前核心运行在Java 8(Oracle)上,我们希望使用Java 21或Java 11来开发REST API.

在处理POC时,我们注意到,特定EJB调用的返回值不能被新的Java版本同步化.

我已经设法创建了一个简化的再现器,它只包含尽可能简化的类来再现问题.那台复制机可以找到overhere台.

如果一个特定的对象图是像在再现器中那样构造的,并且当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上出现了上述错误.查看链接的repo以了解其他试用的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);
        }
    }

推荐答案

因此,基于42的answer,我开始探索另一个StackOverflow问题,它把我带到了一个兔子洞,在openjdk仓库中找到了引入这种行为的提交.

该提交right here是引入相关更改的地方.从相关JBS门票的描述来看,作者并不知道这种副作用. 我找不到任何预构建的二进制文件来证明这个提交" destruct "了数据化,所以我决定在本地构建两个版本.一个具有 destruct 提交(e11aec59a2705854ded7eaf2103d9cde57d81652)(A),另一个来自该提交(a1e2230a4045afb7930f95dca6a351339403f41d)(B)之前的版本. 复制器抛出一个带有构建A的NPE,但与构建B的工作正常.

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

根据JDK—8199664中提到的解决方法,如果我用下面的方法扩展BaseCodeObject类,那么扩展化工作得很好.

    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
    }

我原来的问题已经得到答复.

Java相关问答推荐

通过推送通知向自己发送Matrix消息

如何让HikariCP指标在NewRelic中正确显示?

Saxon 9:如何从Java扩展函数中的net.sf.saxon.expr. XPathContent中获取声明的变量

具有默认分支的JUnit代码覆盖率切换声明

当切换javaFX场景时,stage的大小正在Minimize

Springdoc Whitelabel Error Page with Spring V3

多个Java线程和TreeMap.put()的非预期行为

基本时态运算的ISO-8601周数据表示法

Java FX中的河内之塔游戏-在游戏完全解决之前什么都不会显示

Java LocalTime.parse在本地PC上的Spring Boot中工作,但在Docker容器中不工作

如何让DTO接受空字符串字段,但如果它们不为空,则应用JPA验证?

为什么我的回收视图会显示重复的列表?

由于在生成器模式中使用泛型,lambda表达式中的返回类型错误

Kotlin Val是否提供了与Java最终版相同的可见性保证?

在Frege中,我如何将一个字符串安全地转换为一个可能的Int?

向Java进程发送`kill-11`会引发NullPointerException吗?

从12小时开始的日期模式

将Optionals/null安全添加到嵌套的flatMap/流

类型安全:从 JSONArray 到 ArrayList> 的未经判断的转换

JAVA 正则表达式识别字符串string或字符串内的字符char