7

考虑以下 scala 脚本:

import scala.reflect.internal.util.ScalaClassLoader

object Test {
  def main(args: Array[String]) {
    val classloaderForScalaLibrary = classOf[ScalaClassLoader.URLClassLoader].getClassLoader
    println(classloaderForScalaLibrary)
    val classloaderForTestClass = this.getClass.getClassLoader
    println(classloaderForTestClass)
    this.getClass.getClassLoader.asInstanceOf[ScalaClassLoader.URLClassLoader]
  }
}

输出是:

scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@71c8becc
scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@71c8becc
java.lang.ClassCastException: scala.reflect.internal.util.ScalaClassLoader$URLClassLoader cannot be cast to scala.reflect.internal.util.ScalaClassLoader$URLClassLoader
        at Main$.main(Test.scala:8)
        at Main.main(Test.scala)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at scala.reflect.internal.util.ScalaClassLoader.$anonfun$run$2(ScalaClassLoader.scala:98)
        at scala.reflect.internal.util.ScalaClassLoader.asContext(ScalaClassLoader.scala:32)
...

为什么我不能ScalaClassLoader$URLClassLoader投到ScalaClassLoader$URLClassLoader

在此处输入图像描述

编辑:

运行时:

scala -J-verbose:class Test.scala | grep ScalaClassLoader

输出是:

[Loaded scala.reflect.internal.util.ScalaClassLoader$URLClassLoader from file:/C:/Development/Software/scala-2.12.2/lib/scala-reflect.jar]
...
...
[Loaded scala.reflect.internal.util.ScalaClassLoader$URLClassLoader from file:/C:/DEVELO~1/Software/SCALA-~1.2/lib/scala-reflect.jar]

所以肯定有一些可疑的类加载正在进行。现在试图调查为什么会这样

4

1 回答 1

2

如果您将代码扩展如下:

import scala.reflect.internal.util.ScalaClassLoader

object test {

  def main(args: Array[String]) {
    val cl1 = this.getClass.getClassLoader
    println(cl1)
    val c1 = cl1.getClass 
    println(cl1.getClass)
    println(cl1.getClass.getClassLoader)

    println("-------")

    var c2 = classOf[ScalaClassLoader.URLClassLoader]
    println(c2)
    println(c2.getClassLoader)
    println("-------")
    println(c1 == c2)

  }
}

你会得到以下输出:

scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@5cee5251
类 scala.reflect.internal.util.ScalaClassLoader$URLClassLoader
sun.misc.Launcher$AppClassLoader@4554617c
-------
类 scala.reflect.internal.util。 ScalaClassLoader$URLClassLoader
scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@5cee5251
-------
false

注意匹配哈希@5cee5251。这意味着第一个 Scala 解释器ScalaClassLoader$URLClassLoader使用根 Java 类加载器加载,然后使用该类加载器加载脚本中的所有类,当您ScalaClassLoader$URLClassLoader在代码中请求时,它会加载另一个(已经加载的)ScalaClassLoader$URLClassLoader. 通过这种方式,您的脚本与执行它的“运行时环境”隔离开来。

您可以在ScalaClassLoader.asContext方法中找到一些详细信息,您可以在堆栈跟踪中看到该方法,该方法使用Thread.setContextClassLoader将自身设置为执行脚本的线程的主类加载器。

更新(为什么它可以在 Mac 上工作,但在 Windows 上不工作)

*nix 和Windows 的scalashell 脚本之间的主要区别在于,默认情况下,在 *nix 平台上,标准 Scala 库被添加到引导类路径(参见脚本),而在 Windows 上,它们被添加到“常用类路径”。这很重要,因为它定义了哪个类加载器将被加载:它是根类加载器(表示为好像您调用)还是应用程序类加载器(即 的实例)。这很重要,因为创建了一个using just withoutscala.batusebootcpscala.reflect.internal.util.ScalaClassLoaderscala.tools.nsc.MainGenericRunnernullgetClassLoadersun.misc.Launcher$AppClassLoaderCommonRunner.runScalaClassLoaderurlsparent

def run(urls: Seq[URL], objectName: String, arguments: Seq[String]) {
  (ScalaClassLoader fromURLs urls).run(objectName, arguments)
} 

这意味着“主”的父类加载器ScalaClassLoader将是引导类加载器,而不是sun.misc.Launcher$AppClassLoader因此当您向这个“主”询问ScalaClassLoaderscala.reflect.internal.util.ScalaClassLoader时,它无法在其类加载器链加载的类中找到它,因此必须加载它再次。这就是为什么您的脚本中有两个不同的ScalaClassLoader 类实例的原因。

有两种明显的解决方法(两者都不太好):

  • 更改CommonRunner.runScala 源代码以实际将当前上下文类加载器作为父级传递给新的ScalaClassLoader(可能不是那么容易☺)
  • 更改scala.bat为使用-Xbootclasspath/a:而不是-cpfor %_TOOL_CLASSPATH%。但是查看 usebootcp*nix 脚本中的我可以看到以下评论:
# default to the boot classpath for speed, except on cygwin/mingw/msys because
# JLine on Windows requires a custom DLL to be loaded.
unset usebootcp
if [[ -z "$cygwin$mingw$msys" ]]; then
  usebootcp="true"
fi

所以我怀疑如果你想使用scala.batREPL,将所有 Scala 库移动到引导类路径可能是个坏主意。如果是这种情况,您可能需要创建一个副本scala.bat(例如scala_run_script.bat)更改它并使用它来运行您的 Scala 脚本,从而scala.bat为 REPL 保留标准。

于 2017-05-25T20:23:49.863 回答