反序列化在Scala 2.10中抛出'ClassNotFoundException:JavaConversions $ SeqWrapper'

时间:2013-06-22 01:15:58

标签: scala serialization persistence classloader deserialization

我有一个从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

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

4 个答案:

答案 0 :(得分:2)

没有真正帮助的想法:

  1. 您已经遇到了Scala集合的底层实现的更改,这些更改反映在相同的序列化中。你无法“加载”到2.10,所以你需要一些共同点。

  2. 您可能会遇到每个版本的Scala,因为收藏尚未完全解决。

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

  4. 在java世界中,我会选择JAXB或SDO;或许EclipseLink MOXy。我怀疑MOXy会意识到Scala集合类型。

  5. 我认为你已经看过this

  6. 您的对象图是否可以完全基于核心Java数据类型转换为某些内容?

答案 1 :(得分:1)

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

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

(或者,不是两个类加载器,而是尝试序列化java表单,然后再回过头来。)

我认为这是理查德的#6。它不必完全是java核心,只有兼容的东西。

我会尝试在喝咖啡休息时提出一个例子,但当然需要注意的是,不相容性取决于收集而不是收集的内容。

答案 2 :(得分:0)

这可能会在你脸上爆炸,但你可以尝试在2.10 scala-library.jar之后将2.9 scala-library.jar放在你的类路径上。 然后类加载器应该找到

 scala.collection.JavaConversions$SeqWrapper

,但如果序列化一直持续下去,我会感到惊讶......

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

祝你好运!

流便

答案 3 :(得分:0)

所以这就是最终为我工作的原因,感谢@ 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来实现Class间接并使用来自Apache Commons的ClassLoaderObjectStream,尽管这很简单。我不太高兴我必须制作自己的SeqWrapper副本(原来这是我文件中唯一有用的类),但scala-2.10的scala.collection.convert.Wrappers中的包装类具有不同于它的SerialVersionUIDs。 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而不打破其他任何我希望听到的东西。