【问题标题】:java.io.InvalidClassException:java.io.InvalidClassException:
【发布时间】:2011-11-02 15:41:43
【问题描述】:
InvalidClassException:本地类不兼容:流 classdesc serialVersionUID = -196410440475012755,本地类 serialVersionUID = -6675950253085108747

在以下场景中,我使用 InvalidClassException 进行构造。在这里,我的 EAR 安装在 4 个 Websphere App 服务器中,并在这些服务器之间共享执行。有时我从 POJO 类中得到异常 InvalidClassException,它实现了 Serializable 接口。请对此提供任何线索。我对此一无所知。

【问题讨论】:

    标签: java serialization


    【解决方案1】:

    当一个对象被序列化时,serialVersionUID 与其他内容一起被序列化。

    稍后在反序列化时,从反序列化对象中提取 serialVersionUID 并与加载的类的 serialVersionUID 进行比较。

    如果数字不匹配,则抛出 InvalidClassException。

    【讨论】:

      【解决方案2】:

      好的,所以我会举一个例子和我的作品:

      Here is a list 和 PoJos 各种序列化形式的性能指标

      您必须权衡性能和便利性。但是,既然我说“JSON”是一种序列化的手段,那么这里是一个不依赖于编译器的简单示例。基本上,除非您在接收端更改了 pojo 的结构,否则编译它的时间/方式/位置完全无关紧要(实际上,它甚至不必在 2 个 JVM 之间)。正如您从链接中看到的,JSON 实际上是最慢的,而 XML 只是一个负载猪。但它们都具有得到普遍支持的决定性优势。 XML 甚至允许应用样式表。

          <dependency>
              <groupId>com.google.code.gson</groupId>
              <artifactId>gson</artifactId>
              <version>2.3.1</version>
              <scope>test</scope>
          </dependency>
      

      代码

        @Test
          public void testJSON() throws Exception {
              Foo expected = new Foo(1,"Christian",1000000.00d);
              Gson gson = new GsonBuilder().setPrettyPrinting().create();
              String testJson = gson.toJson(expected);
      
              System.out.println(testJson);
      
              Foo result = gson.fromJson(testJson, Foo.class);
              assertEquals(expected,result);
      
          }
      
          public static class Foo {
      
              private String name;
              private Integer age;
              private Double paycheck;
      
              public Foo(Integer age, String name, Double paycheck) {
                  this.age = age;
                  this.name = name;
                  this.paycheck = paycheck;
              }
      
              @Override
              public boolean equals(Object o) {
                  if (this == o) return true;
                  if (o == null || getClass() != o.getClass()) return false;
      
                  Foo foo = (Foo) o;
      
                  if (age != null ? !age.equals(foo.age) : foo.age != null) return false;
                  if (name != null ? !name.equals(foo.name) : foo.name != null) return false;
                  if (paycheck != null ? !paycheck.equals(foo.paycheck) : foo.paycheck != null) return false;
      
                  return true;
              }
      
              @Override
              public int hashCode() {
                  int result = name != null ? name.hashCode() : 0;
                  result = 31 * result + (age != null ? age.hashCode() : 0);
                  result = 31 * result + (paycheck != null ? paycheck.hashCode() : 0);
                  return result;
              }
          }
      

      输出

      {
        "name": "Christian",
        "age": 1,
        "paycheck": 1000000.0
      }
      

      【讨论】:

        【解决方案3】:

        如果有人再次遇到与 EJB TimerTask 相关的这个问题 - 就像我刚才所做的那样 - 如果您正在使用 WebSphere Application Server,这里有一个提示:

        配置文件的bin文件夹中有2个批处理文件/shell脚本,可以列出和删除EJB定时器任务。就我而言,我有一些计时器任务用不同的、过时的 serialVersionUID 序列化对象。我无法摆脱它们,因为序列化对象确实发生了变化。所以我只用了那些:

        findEJBTimers.bat cancelEJBTimers.bat

        您的计时器任务消失了,错误消息也消失了。就我而言,这正是我所需要的,但很难获得这些信息。

        【讨论】:

          【解决方案4】:

          当您实现 java.io.Serializable 接口以使类可序列化时,编译器会查找名为“serialVersionUID”的 long 类型的静态最终字段。如果该类没有显式声明此字段,则编译器将创建一个此类字段并为其分配一个值,该值来自与实现相关的 serialVersionUID 计算。这种计算取决于类的各个方面,它遵循 Sun 给出的对象序列化规范。但是,不能保证该值在所有编译器实现中都相同。

          该值用于检查类在序列化方面的兼容性,这是在反序列化保存的对象时完成的。序列化运行时验证发送者类的 serialVersionUID(用于保存流上对象的状态)和接收者类(用于恢复对象的类,可能在其他系统中)的 serialVersionUID两者完全相同。如果接收系统加载的类具有与序列化过程中使用的类不同的 serialVersionUID,那么我们会得到 InvalidClassException。

          注意—— 强烈建议您在要使其可序列化的所有类中显式声明和初始化 long 类型的静态 final 字段并命名为“serialVersionUID”,而不是依赖于该字段值的默认计算。这种计算非常敏感,并且可能因编译器实现而异,因此即使对于同一个类,您也可能会因为您在序列化过程的发送端和接收端使用不同的编译器实现而出现 InvalidClassException。

          在大多数情况下,您只会对该字段使用“私有”访问说明符,因为声明通常仅适用于声明它的类,我们实际上不需要在子类中继承该字段或访问它从外面。因此,我们几乎没有理由不将其保持为“私有”。

          【讨论】:

          • 您好,非常感谢您的回复。正如您提到的序列化 uid 特定于编译器。但是这里我的编译器版本在所有系统中都是相同的,并且间歇性地发生。
          • 我不确定接下来发生了什么...我建议您检查谷歌上可用的相关链接并尝试将您的代码与它们相关联。当你得到一些东西时做评论。 (好 2 看到来自班格罗尔的 somone,我来自浦那。)
          • 我现在也面临同样的问题,编译器版本不匹配是唯一的原因吗?我在两台计算机(即开发和发布机器)上有 java 1.6.29 和 1.6.23 版本
          • @Akhilesh 这真的很难说。但是编译器版本不匹配可能是一种可能的情况。我宁愿建议您提供代码并在这里询问。
          • 感谢您的解释 - 对应该是什么值有任何限制,即我可以将它们都设置为零吗?另外,有谁知道这个ID的用途是什么?是否检查该类是否正确解码?如果是这样,那么为什么要如此敏感,即为什么不只是散列值?我想我要去哪里,覆盖这个字段对我来说危险吗?我是否冒着绕过编译器之间真正不兼容的测试的风险?
          【解决方案5】:

          当您尝试反序列化使用同一类的不兼容(通常较早)版本序列化的对象时,您会遇到该异常。

          如果您没有在实现Serializable 的类中明确指定serialVersionUID,则将根据您的类的(非transient)字段生成一个值。这样做是为了确保没有部分对象被恢复(最好是失败而不是盲目地继续处理可能损坏的对象)。

          在 Web 应用程序系统中,序列化的常见用途是会话:如果您将一个值放入会话中,它很可能最终会被序列化(用于集群支持或只是为了获得持久会话)。

          所以要么保持所有类在版本之间兼容确保不恢复它们不会破坏你的应用程序(即不要以这种方式存储重要信息)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-04-18
            • 2015-07-21
            • 2013-12-08
            • 2020-01-06
            • 1970-01-01
            相关资源
            最近更新 更多