When I "sbt run" the following code,
package com.example
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.FileInputStream
import java.io.FileOutputStream
object SimpleFailure extends App {
case class MyClass(a: String, b: Int, c: Double)
def WriteObjectToFile[A](obj: A, filename: String) {
val output = new Obj开发者_JAVA百科ectOutputStream(new FileOutputStream(filename, false))
output.writeObject(obj)
}
def ReadObjectFromFile[A](filename: String)(implicit m: Manifest[A]): A = {
val obj = new ObjectInputStream(new FileInputStream(filename)) readObject
obj match {
case a if m.erasure.isInstance(a) => a.asInstanceOf[A]
case _ => { sys.error("Type not what was expected when reading from file") }
}
}
val orig = MyClass("asdf", 42, 2.71)
val filename = "%s/delete_me.spckl".format(System.getProperty("user.home"))
WriteObjectToFile(List(orig), filename)
val loaded = try {
ReadObjectFromFile[List[MyClass]](filename)
} catch { case e => e.printStackTrace; throw e }
println(loaded(0))
}
I get the following exception:
java.lang.ClassNotFoundException: com.example.SimpleFailure$MyClass
However, I can run the code fine in Eclipse with the Scala plugin. Is this an SBT bug? Interestingly, the problem only comes up when wrapping MyClass in a List (see how "orig" is wrapped in a List in the WriteObjectToFile call). If I don't wrap in a List, everything works fine.
Put this in your build.sbt or project file:
fork in run := true
The problem seems to be with the classloader that gets used when sbt loads your code. ObjectInputStream describes it's default classloader resolution, which walks the stack. Normally, this ends up finding the loader associated with the program in mind, but in this case, it ends up using the wrong one.
I was able to work around this by including the following class in my code, and using it instead of ObjectInputStream directly.
package engine;
import java.io.InputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
class LocalInputStream extends ObjectInputStream {
LocalInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws ClassNotFoundException
{
return Class.forName(desc.getName(), false,
this.getClass().getClassLoader());
}
}
This overrides the resolveClass method, and always uses one associated with this particular class. As long as this class is the one that is part of your app, this should work.
BTW, this is both faster than requiring fork in run, but it also works with the Play framework, which currently doesn't support forking in dev mode.
I was able to reproduce this too using sbt 0.10.1 and scalaVersion := "2.9.0-1"
. You should probably just report it on github or bring it up on the mailing list.
精彩评论