【问题标题】:Serialization incompatibility between class generated by ajc and javacajc和javac生成的类的序列化不兼容
【发布时间】:2019-09-17 13:56:47
【问题描述】:

最近我发现一些由 Java (Java 8) 和 ajc (v.1.9.2) 编译的类不兼容序列化。序列化兼容性是指计算出的默认 serialVersionUID 不同。

例子:

public class Markup implements Serializable {
    private final MyUnit unit;
    public Markup(MyUnit unit) { this.unit = unit; }

    public enum MyUnit { DOUBLE, STRING }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Path path = Paths.get("markup.bin");
        if (args.length == 0) {
            try (OutputStream fileOutput = Files.newOutputStream(path);
                 ObjectOutputStream objectOutput = new ObjectOutputStream(fileOutput))
            {
                objectOutput.writeObject(new Markup(MyUnit.STRING));
            }
        } else {
            try (InputStream fileInput = Files.newInputStream(path);
                 ObjectInputStream objectInput = new ObjectInputStream(fileInput))
            {
                System.out.println(objectInput.readObject());
            }
        }
    }

    static String switchType(MyUnit unit) {
        switch (unit) {
            case STRING: return "%";
            case DOUBLE: return "p";
            default: return "Undefined";
        }
    }
}

当我通过 ajc 编译这个类并运行,然后通过 javac 编译这个类并运行时,我得到关于序列化格式不兼容的异常:

Exception in thread "main" java.io.InvalidClassException: Markup; local class incompatible: stream classdesc serialVersionUID = -1905477862550005139, local class serialVersionUID = 793529206923536473

我还发现这是因为 ajc 开关代码生成器。它在 private static int[] $SWITCH_TABLE$Markup$MyUnit 类中创建附加字段

javac 生成的字段: ajc 生成的字段:

我的问题是:

  1. Java 编译器规范是否允许生成类中未定义的字段?
  2. 为什么 ajc 会生成额外的字段?某种性能优化?
  3. 有没有办法让ajc不生成额外的字段?
  4. private static 影响 serialVersionUID 生成的原因是什么?
  5. aspectj 的开发人员是否知道这种行为?如果是这样,为什么他们仍然选择生成字段?
  6. 是否有任何保证 JLS 将如何序列化 Java 类?
  7. 没有这个字段,Javac 生成的代码如何工作?

【问题讨论】:

  • 1.是的。 2. 看起来像一个开关表。 3. 没有。 4. 不清楚。你需要自己指定serialVersionUID来解决这个问题。
  • 5.见 4。 6. 是的,有一个完整的对象序列化规范。 7. 太宽泛。你总是可以按照我建议的方式尝试,而不是仅仅添加更多毫无意义的问题。每个帖子一个问题是这里通常的最大值。
  • >6.是的,有一个完整的对象序列化规范。为什么在这种情况下它不能很好地工作?如果有规范 - 这意味着某些编译器会忽略它。哪一个?我想知道更深入的细节。不仅仅是是/否的答案。
  • 我不认为澄清问题毫无意义。它们有助于解释背景——我不想知道的。
  • §4.6 of the Java Object Serialization Specification;它明确警告默认 serialVersionUID 计算对编译器实现高度敏感。它还解释了如何计算默认值。请注意,Serializable 文档也有相同的警告。

标签: java serialization aspectj javac serialversionuid


【解决方案1】:

1. java编译器规范是否允许生成类中未定义的字段?

是的,尽管它们应该被标记为合成的。 Synthetics 包括内部类的访问器(尽管该实现最近发生了变化)、lambda 表达式方法以及我认为的默认构造函数。

2。为什么 ajc 会生成额外的字段?某种性能优化?

看起来像一个糟糕的性能优化。也许这与有效地添加方面有关。

3。有什么方法可以让ajc不生成额外的字段?

不知道。无论如何,您都应该期望生成合成物。

4. private static 影响 serialVersionUID 生成的原因是什么?

Java 序列化是在“互联网时代”创建的。为了兼容性,我们保留了第一个版本发生的任何事情。道德:如果你在互联网时代创造了任何东西,一定要把它扔掉。

5. aspectj 的开发人员是否知道这种行为?如果是这样,为什么他们仍然选择生成字段?

我希望如此。编译器预计会生成合成。

6.是否有任何保证 JLS 将如何序列化 Java 类?

有一个 Java 序列化规范(虽然我真的不希望有很多人阅读它)。

7.没有此字段,Javac 生成的代码如何工作?

很容易看出如何在没有数组的情况下编写开关字符串。丑陋的优化版本的确切细节会很混乱。您可以看到javap -private -c 实际发生的情况。

结论

如果希望在类的不同版本之间使用数据,建议添加serialVersionUID。 OTOH,也建议不要使用Java序列化。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-07
    • 1970-01-01
    • 2012-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多