Vaadin - Tomcat 上的可序列化错误

Vaadin - serializable errors on Tomcat

我的示例应用程序配置:Java 8,Vaadin 7.5.6; 部署在 Apache Tomcat 8.0.24.

重新部署后,我有时会在服务器启动时遇到一些异常。例如今天启动时:

2015-10-29 09:54:15 ERROR StandardManager - standardManager.loading.ioe
java.io.InvalidClassException: com.vaadin.server.WebBrowser; local class incompatible: stream classdesc serialVersionUID = -4707470459521903161, local class serialVersionUID = 1931594131304735556
        at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2000) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.defaultReadObject(ObjectInputStream.java:501) ~[?:1.8.0_51]
        at com.vaadin.server.VaadinSession.readObject(VaadinSession.java:1443) ~[vaadin-server-7.5.6.jar:?]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_51]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_51]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_51]
        at java.lang.reflect.Method.invoke(Method.java:497) ~[?:1.8.0_51]
        at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1017) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371) ~[?:1.8.0_51]
        at org.apache.catalina.session.StandardSession.doReadObject(StandardSession.java:1634) ~[catalina.jar:8.0.24]
        at org.apache.catalina.session.StandardSession.readObjectData(StandardSession.java:1099) ~[catalina.jar:8.0.24]
        at org.apache.catalina.session.StandardManager.doLoad(StandardManager.java:261) [catalina.jar:8.0.24]
        at org.apache.catalina.session.StandardManager.load(StandardManager.java:180) [catalina.jar:8.0.24]
        at org.apache.catalina.session.StandardManager.startInternal(StandardManager.java:460) [catalina.jar:8.0.24]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:8.0.24]
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5184) [catalina.jar:8.0.24]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:8.0.24]
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:725) [catalina.jar:8.0.24]
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:701) [catalina.jar:8.0.24]
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717) [catalina.jar:8.0.24]
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:945) [catalina.jar:8.0.24]
        at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1768) [catalina.jar:8.0.24]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_51]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_51]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0_51]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0_51]
        at java.lang.Thread.run(Thread.java:745) [?:1.8.0_51]
2015-10-29 09:54:15 ERROR StandardManager - Exception loading sessions from persistent storage
java.io.InvalidClassException: com.vaadin.server.WebBrowser; local class incompatible: stream classdesc serialVersionUID = -4707470459521903161, local class serialVersionUID = 1931594131304735556
        at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2000) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.defaultReadObject(ObjectInputStream.java:501) ~[?:1.8.0_51]
        at com.vaadin.server.VaadinSession.readObject(VaadinSession.java:1443) ~[vaadin-server-7.5.6.jar:?]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_51]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_51]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_51]
        at java.lang.reflect.Method.invoke(Method.java:497) ~[?:1.8.0_51]
        at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1017) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351) ~[?:1.8.0_51]
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371) ~[?:1.8.0_51]
        at org.apache.catalina.session.StandardSession.doReadObject(StandardSession.java:1634) ~[catalina.jar:8.0.24]
        at org.apache.catalina.session.StandardSession.readObjectData(StandardSession.java:1099) ~[catalina.jar:8.0.24]
        at org.apache.catalina.session.StandardManager.doLoad(StandardManager.java:261) ~[catalina.jar:8.0.24]
        at org.apache.catalina.session.StandardManager.load(StandardManager.java:180) ~[catalina.jar:8.0.24]
        at org.apache.catalina.session.StandardManager.startInternal(StandardManager.java:460) [catalina.jar:8.0.24]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:8.0.24]
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5184) [catalina.jar:8.0.24]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:8.0.24]
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:725) [catalina.jar:8.0.24]
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:701) [catalina.jar:8.0.24]
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717) [catalina.jar:8.0.24]
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:945) [catalina.jar:8.0.24]
        at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1768) [catalina.jar:8.0.24]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_51]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_51]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0_51]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0_51]
        at java.lang.Thread.run(Thread.java:745) [?:1.8.0_51]

第二次启动后一切正常 - 没有错误。实际上我发现了一个 solution on SO - 这是 Tomcat 会话在重启后的持久性问题。

但我查看了 WebBrowser 代码,实际上并没有 serialVersionUID 变量,即使这个 class 实现了 Serializable。当然可以,因为这个变量是generated using hash function。但这是第一个小问题:它真的正确还是一种错误?

主要问题:

假设使用Vaadin,如何防止此类异常?我应该禁用 Tomcat 会话持久性还是应该在我的代码中搜索一些 bug/problems?

前言:此答案假定您的 class 中的一些 serialized/persisted 在部署之间发生变化。

我当然记得我第一次 运行 使用 Tomcat 时挠头。就像您一样,我们也允许为序列化的 classes 自动生成 serialVersionUID,这是 class 成员的某种哈希值。因此,如果这些 classes 以任何方式发生变化,自动生成的 serialVersionUID 也会随之改变。这才是你真正想要的。

所以长话短说,系统正在做它应该做的事情(根本不是真正的错误)。对象的持久序列化版本与新部署中的 classes 不匹配——因此在尝试反序列化它们时会出现异常。然后它们是 consumed/discarded,这就是它在第二次重启时起作用的原因。

一种有点残酷的部署策略可能是在部署新的 war 之前销毁 Tomcat 下的 work\Catalina\localhost\yourapplicationname 目录。如果 Tomcat 下还有其他应用程序 运行 :)

,则销毁整个工作目录将是一种反社会行为

当然,您可以尝试添加对反序列化多个 "versions" 对象的支持,但这将非常非常难以维护。