【问题标题】:Deserialization throws 'ClassNotFoundException: JavaConversions$SeqWrapper' in Scala 2.10反序列化在 Scala 2.10 中抛出 'ClassNotFoundException: JavaConversions$SeqWrapper'
【发布时间】:2013-06-19 05:51:34
【问题描述】:

我有一个从 Scala-2.9 序列化出来的相当复杂的对象图,我需要将它读入 Scala-2.10。然而,在对象图 Scala-2.10 的某个深处抛出:

! java.lang.ClassNotFoundException: scala.collection.JavaConversions$SeqWrapper
! at java.net.URLClassLoader$1.run(URLClassLoader.java:366) ~[na:1.7.0_21]
! at java.net.URLClassLoader$1.run(URLClassLoader.java:355) ~[na:1.7.0_21]
! at java.security.AccessController.doPrivileged(Native Method) ~[na:1.7.0_21]
! at java.net.URLClassLoader.findClass(URLClassLoader.java:354) ~[na:1.7.0_21]
! at java.lang.ClassLoader.loadClass(ClassLoader.java:423) ~[na:1.7.0_21]
! at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) ~[na:1.7.0_21]
! at java.lang.ClassLoader.loadClass(ClassLoader.java:356) ~[na:1.7.0_21]
! at java.lang.Class.forName0(Native Method) ~[na:1.7.0_21]
! at java.lang.Class.forName(Class.java:266) ~[na:1.7.0_21]
! at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:623) ~[na:1.7.0_21]
...

将这个序列化对象加载到 Scala-2.10 中的最简单方法是什么? 该对象使用 Scala-2.9 正确反序列化,但看起来标准库中的东西已经移动了。 scala.collection.JavaConversions的大部分成员现在都在scala.collection.convert.Wrappers

展望未来,我还对持久化大型复杂对象图的更强大的方法感兴趣,而无需为所涉及的每个类显式指定序列化。

【问题讨论】:

  • 感谢@som-snytt。我在 ScalaDays 上看到了 Heather 的演讲。 Scala-pickling 看起来很棒,但它还没有准备好用于生产。
  • 什么版本的Java?估计双方都一样。 SeqWrapper 只是 java 集合的包装器。如果不排除明显的类路径问题,我不会推测 CNFE。
  • 草草评论关于 SeqWrapper 的评论——有人问到 2.9,我还在那个分支上——我必须看 hulu 的 101 天的夏季大师。

标签: scala serialization persistence classloader deserialization


【解决方案1】:

所以这就是最终对我有用的东西,感谢@som-snytt 为我指明了正确的方向:

object MyWrappers {
  import java.{ lang => jl, util => ju }, java.util.{ concurrent => juc }
  import scala.collection.convert._
  import WrapAsScala._
  import WrapAsJava._
  import Wrapper._

  @SerialVersionUID(3200663006510408715L)
  case class SeqWrapper[A](underlying: Seq[A]) extends ju.AbstractList[A] with Wrappers.IterableWrapperTrait[A] {
    def get(i: Int) = underlying(i)
  }
}

import org.apache.commons.io.input.ClassLoaderObjectInputStream
object Loader extends ClassLoader {
  override def loadClass(name: String) : Class[_] = {
    import javassist._
    try super.loadClass(name)
    catch {
      case e: ClassNotFoundException if name.startsWith("scala.collection.JavaConversions") => {
            val name2 = name.replaceFirst("scala.collection.JavaConversions",
                                          "MyWrappers")
            val cls = ClassPool.getDefault().getAndRename(name2, name)
            cls.toClass()
          }
    }
  }
}

val objectStream = new ClassLoaderObjectInputStream(Loader, stream)
objectStream.readObject()

这使我可以将原始 2.9 序列化文件直接读入 2.10,而无需重新序列化。它依赖于 Javassist 来执行类间接并使用来自 Apache Commons 的 ClassLoaderObjectStream,尽管这很容易让您自己滚动。我很不高兴我不得不制作自己的 SeqWrapper 副本(结果证明这是我文件中唯一有问题的类),但是 scala-2.10 的 scala.collection.convert.Wrappers 中的包装类与相应的类具有不同的 SerialVersionUID在 2.9 的 scala.collection.JavaConversions 中,即使来源在文本上是相同的。我最初尝试只是重定向到 scala.collection.convert.Wrappers 并使用 Javassist 设置 SerialVersionUID:

object Loader extends ClassLoader {
  override def loadClass(name: String) : Class[_] = {
    import javassist._
    try super.loadClass(name)
    catch {
      case e: ClassNotFoundException if name.startsWith("scala.collection.JavaConversions") => {
            val name2 = name.replaceFirst("JavaConversions", "convert.Wrappers")
            val cls = ClassPool.getDefault().getAndRename(name2, name)
            cls.addField(CtField.make("private static final long serialVersionUID = 3200663006510408715L;", cls))
            cls.toClass()
          }
    }
  }
}

这使我可以毫无例外地读取序列化文件,但是以这种方式读取的对象是不完整的。 (如果这种方法有效并且文件中存在多个问题类,我真的需要一个 SerialVersionUID 的查找表,但这不是重点)。如果有人知道如何在 Javassist 生成的类上设置 SerialVersionUID 而不会破坏其他任何东西,我想听听。

【讨论】:

    【解决方案2】:

    请不要下意识地投票,但我的想法是在一个类加载器中反序列化(在 scala 2.9 上),转换为 java 集合,然后在第二个类加载器中(类路径上有 2.10)从 java 转换回 scala。

    换句话说,java 是通用格式(java 运行时对两个类加载器都是通用的)。

    (或者,尝试序列化 java 表单,而不是两个类加载器,然后将其转回。)

    我认为这属于 Richard 的 #6。它不必完全是 java 核心,只要是兼容的即可。

    我会尝试在喝咖啡的时候举一个例子,但当然需要注意的是,不兼容在于集合而不是集合。

    【讨论】:

    • 谢谢!!对于使用 aClassLoader 来解决这个问题,我有一个完全的盲点。我想我实际上可以做一些更简单的事情:只需编写一个加载程序来拦截有问题的类的加载并重定向到它们的 2.10 等效项并消除SerialVersionUID 的差异。这样我应该能够直接将原始文件读入 2.10 而无需重新序列化。这将接近我的最佳案例方案解决方案。
    • @DanielMahler 对于集合的具体问题,我曾设想过生成 java 集合,但这不是内置工具; JavaConverters 是包装器;但是你已经有一个包装好的 java 集合,所以打开它可能更容易。我没有考虑关于课程的特殊知识。
    • 使用 ClassLoaders 的建议让我找到了一个可行的解决方案。我的实际解决方案是不同的(因为建议的方法也可以),但这个答案是我需要的灯泡。谢谢!!
    • 好的,我为你感到高兴,@DanielMahler!我还没查过,但是过去时“led”的拼写是“fed”,而不是“read”(读作“red”)或 metal lead。如果我可以选择一个超级大国,那将不是飞行,而是立即映射 { case "lead" if past => "led" } 的能力。小时候,当我自言自语时,我把“misled”发音为“missled”。也许这就是原因。
    • @DanielMahler 顺便说一句,大约十年前,我对一个项目做了类似的事情,升级了产品版本并破坏了已安装基础的序列化数据。我有一种中间语言来描述如何转换序列化形式。
    【解决方案3】:

    这可能会让您大吃一惊,但您可以尝试将 2.9 scala-library.jar 放在类路径中,放在 2.10 scala-library.jar 之后。 然后类加载器应该找到

     scala.collection.JavaConversions$SeqWrapper
    

    ,但是如果序列化一直通过,我会感到惊讶......

    您应该考虑只使用 gson 或 jackson 或 lift-json 序列化为 json 管他呢。这很乏味,但你做一次,它就完成了,它会与 其他语言也是 - 你的“快速” 解决方案将意味着反复出现的痛苦……

    祝你好运!

    鲁本

    【讨论】:

      【解决方案4】:

      想法,没有真正的帮助:

      1. 您遇到了 Scala 集合的底层实现的变化,这反映在相同的序列化中。您无法将其“加载”到 2.10,因此您需要一些共同点。

      2. 您可能会在每个版本的 Scala 中遇到这种情况,因为 Collections 尚未完全解决。

      3. 我认为您的意图是使用基于 2.9 的代码加载图表,转换为某种新格式,然后以新的“通用”格式转储。

      4. 在 Java 世界中,我会使用 JAXB 或 SDO;也许EclipseLink MOXy。我怀疑 MOXy 会知道 Scala 集合类型。

      5. 我想你已经看过this

      6. 可以将您的对象图转换为完全基于核心 Java 数据类型的东西吗?

      【讨论】:

      • 感谢@RichardSitze。我怀疑我将不得不在 2.9 中重新加载并重新序列化为 bot 2.9 和 2.10 可以理解的东西。然而,我正在处理相当复杂的应用程序对象,所以我正在寻找最轻松的解决方案,即不必明确说明如何序列化和反序列化所涉及的每个类和字段。我也有一些希望,我可以在 2.10 中使用新的 2.10 反射 api 直接拦截 SeqWrapper 反序列化,但这可能是不可能的。
      猜你喜欢
      • 1970-01-01
      • 2012-09-17
      • 1970-01-01
      • 2013-04-04
      • 1970-01-01
      • 2020-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多