SBT 中 运行 代码时的 ClassCastException

ClassCastException when running code in SBT

我的代码大致如下所示:

val classLoader = new URLClassLoader(entry.jars, Thread.currentThread.getContextClassLoader)
val env = classLoader.loadClass(entry.name).asSubclass(classOf[Environment])

对于上下文:"entry" 是 JAR 文件和 class 名称的集合。 class 预计会延长 EnvironmentEnvironment 在同一个包中定义,不使用任何 class 加载技巧。 JAR 文件集合仅包含指定 class 所需的 classes,仅此而已(尤其是任何父 ClassLoader 已经加载的内容)。

当我 运行 sbt 中的这段代码和 run 时,一切正常。但是如果我在同一个 sbt shell 中第二次 运行 它,我得到:

[error] (run-main-1) java.lang.ClassCastException: class edu.tum.cs.isabelle.impl.Environment
java.lang.ClassCastException: class edu.tum.cs.isabelle.impl.Environment
    at java.lang.Class.asSubclass(Class.java:3404)

我不明白这里的问题:在 运行 之间没有重新编译或任何东西。我为 test 执行了类似的代码,并且在那里工作正常。当我将 fork 设置为 true 时它确实有效,但我没有理由必须这样做。

为了调试,我检查了 getClassLoaderclassOf[Environment] 和加载的 class 的超级 class。果然,在第一个运行中,它们是等价的,而在第二个运行中,它们是等价的。不过,他们的 toString 表示是相同的。

编辑: 更多调试。我已经打印出所有涉及的 classloaders:

System.identityHashCode
println(s"self: " + System.identityHashCode(classOf[Environment].getClassLoader))
println(s"fresh: " + System.identityHashCode(classLoader))
println(s"impl: " + System.identityHashCode(classLoader.loadClass(entry.name).getClassLoader))
println(s"impl.parent: " + identityHashCode(classLoader.loadClass(entry.name).getSuperclass.getClassLoader))

第一个运行的结果:

self: 1209891953
fresh: 1734438968
impl: 1734438968
impl.parent: 1209891953

第二个结果运行:

self: 1952422714
fresh: 1110295313
impl: 1110295313
impl.parent: 1209891953

Rob Norris 在别处提出了解决方案。由于当前Thread的classloader可以是"literally anything",所以我应该确保使用用来加载Environment的classloader。果然,如果我把代码改成

val classLoader = new URLClassLoader(entry.jars, getClass.getClassLoader)

代码按预期运行。