为什么SBT的线程上下文类加载器无法将JDK类文件作为资源加载?

5
lihaoyi test$ tree
.
└── Foo.scala

0 directories, 1 file

lihaoyi test$ cat Foo.scala
object Main{
  def main(args: Array[String]): Unit = {
    println(getClass.getClassLoader.getResourceAsStream("java/lang/String.class"))
    println(getClass.getClassLoader.getClass)
    println(Thread.currentThread().getContextClassLoader.getResourceAsStream("java/lang/String.class"))
    println(Thread.currentThread().getContextClassLoader.getClass)
  }
}

lihaoyi test$ sbt run
[info] Loading global plugins from /Users/lihaoyi/.sbt/0.13/plugins
[info] Set current project to test (in build file:/Users/lihaoyi/Dropbox/Workspace/test/)
[info] Updating {file:/Users/lihaoyi/Dropbox/Workspace/test/}test...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Compiling 1 Scala source to /Users/lihaoyi/Dropbox/Workspace/test/target/scala-2.10/classes...
[info] Running Main
sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@18e38ff2
class sbt.classpath.ClasspathUtilities$$anon$1
null
class sbt.classpath.ClasspathFilter
[success] Total time: 2 s, completed 29 May, 2017 4:14:11 PM

lihaoyi test$

在这里,我们可以看到getClass.getClassLoaderThread.currentThread.getContextClassLoader返回不同的值。更重要的是,Thread.currentThread.getContextClassLoader似乎拒绝加载java/lang/String.class,而另一个可以。

值得注意的是,当我使用像scalac/scalajava这样的外部工具运行jar文件时,两个类加载器都能够将类文件作为资源加载。
lihaoyi test$ scalac Foo.scala

lihaoyi test$ scala Main
sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@1b28cdfa
class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader
sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@7229724f
class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader

我希望SBT的行为类似:可以使用Main.getClass.getClassLoaderThread.currentThread().getContextClassLoader来加载java/lang/String.class资源。为什么呢?

2个回答

3

以下是Jason Zaugg(retronym)在他的sbt启动器笔记中提供的一些提示。

sbt/launcher是一个小型Scala应用程序,它可以通过Ivy依赖项解析引导描述为配置文件的任意Scala程序(通常为SBT)。

这将创建一个包含Scala 2.10.6的子类加载器。该子类加载器包含SBT本身和xsbti/interface-0.13.11.jar。

SBT需要使用非标准的类加载器委派来有选择地隐藏用于插件代码、Scala编译器或用户代码的子类加载器中的类。

sbt 0.13源代码中的更多提示:

def makeLoader(classpath: Seq[File], instance: ScalaInstance, nativeTemp: File): ClassLoader =
  filterByClasspath(classpath, makeLoader(classpath, instance.loader, instance, nativeTemp))


def makeLoader(classpath: Seq[File], parent: ClassLoader, instance: ScalaInstance, nativeTemp: File): ClassLoader =
  toLoader(classpath, parent, createClasspathResources(classpath, instance), nativeTemp)

基本上,sbt是一个Java应用程序的综合体,其中包含任意版本的Scala、您的代码、测试库以及Oracle/OpenJDK的Java库。为了构建一个有意义的类路径,避免反复加载它们,它创建了一组按某些标准过滤的类加载器层次结构。(我想)


2
值得注意的是,解决这个问题的一种方法是设置
(fork in run) := true,

(connectInput in run) := true,
(outputStrategy in run) := Some(StdoutOutput),

看起来这解决了问题,(Thread.currentThread().getContextClassLoader.getResourceAsStream("java/lang/String.class") 现在可以正常工作),但同时也引入了其他无关的问题(分叉的JVM需要一段时间来启动,启动时很冷并且需要时间来热身...)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接