开发者

Scala SBT: possible bug?

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜