我正在(作为团队的一员)开发一个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);
}
}