Scala 脚本:解释此 class 将错误转换为 Windows 上的 ScalaClassLoader
Scala Script: Explain this class cast error to ScalaClassLoader on Windows
考虑以下 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(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]
所以肯定有一些可疑的 class 加载正在进行。现在试图调查为什么会这样
如果您按以下方式进一步扩展代码:
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
class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader
sun.misc.Launcher$AppClassLoader@4554617c
-------
class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader
scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@5cee5251
-------
false
注意匹配的哈希 @5cee5251
。
这意味着第一个 Scala 解释器加载 ScalaClassLoader$URLClassLoader
使用根 Java class 加载器然后使用那个 class 加载器加载脚本中的所有 classes 以及何时你在你的代码中请求 ScalaClassLoader$URLClassLoader
它加载了 ScalaClassLoader$URLClassLoader
的另一个(已经加载的)实例。通过这种方式,您的脚本与执行它的 "runtime environment" 隔离。
您可以在 ScalaClassLoader.asContext method, that you can see in your stack trace, that uses Thread.setContextClassLoader 找到一些详细信息,以将其自身设置为执行脚本的线程的主要 class加载程序。
Update(为什么它适用于 Mac 但不适用于 Windows)
*nix 的 scala
shell 脚本和 Windows 的 scala.bat
脚本之间的主要区别是默认情况下在 *nix 平台上标准 Scala 库被添加到引导Class路径(参见脚本中的 usebootcp
),而在 Windows 上,它们被添加到 "Usual Classpath"。这很重要,因为它定义了哪个 class 加载器将加载 scala.tools.nsc.MainGenericRunner
使用的 scala.reflect.internal.util.ScalaClassLoader
:它将是根 class 加载器(表示为 null
如果您调用 getClassLoader
) 或应用程序 Class 加载程序(即 sun.misc.Launcher$AppClassLoader
的实例)。这很重要,因为 CommonRunner.run
仅使用 urls
而没有 parent
创建 ScalaClassLoader
的实例
def run(urls: Seq[URL], objectName: String, arguments: Seq[String]) {
(ScalaClassLoader fromURLs urls).run(objectName, arguments)
}
这意味着 "main" ScalaClassLoader
的父 class 加载程序将是引导 class 加载程序而不是 sun.misc.Launcher$AppClassLoader
因此当你问这个"main" ScalaClassLoader
对于 class scala.reflect.internal.util.ScalaClassLoader
它无法在其 class 加载程序链加载的 class 中找到它,因此必须加载它再次。这就是为什么您的脚本中有两个不同的 ScalaClassLoader
class 实例的原因。
有两个明显的解决方法(但都不太好):
- 更改 Scala 源代码中的
CommonRunner.run
以实际将当前上下文 class 加载器作为父级传递给新的 ScalaClassLoader
(可能没那么容易 ☺)
- 将
scala.bat
更改为对 %_TOOL_CLASSPATH%
使用 -Xbootclasspath/a:
而不是 -cp
。但是查看 *nix 脚本中的 usebootcp
我可以看到以下评论:
# 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
所以我怀疑如果您想对 REPL 使用 scala.bat
,将所有 Scala 库移动到 Boot Class 路径可能不是一个好主意。如果是这种情况,您可能需要创建 scala.bat
的副本(例如 scala_run_script.bat
)更改它并将其用于 运行 您的 Scala 脚本,留下标准 scala.bat
REPL.
考虑以下 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(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]
所以肯定有一些可疑的 class 加载正在进行。现在试图调查为什么会这样
如果您按以下方式进一步扩展代码:
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
class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader
sun.misc.Launcher$AppClassLoader@4554617c
-------
class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader
scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@5cee5251
-------
false
注意匹配的哈希 @5cee5251
。
这意味着第一个 Scala 解释器加载 ScalaClassLoader$URLClassLoader
使用根 Java class 加载器然后使用那个 class 加载器加载脚本中的所有 classes 以及何时你在你的代码中请求 ScalaClassLoader$URLClassLoader
它加载了 ScalaClassLoader$URLClassLoader
的另一个(已经加载的)实例。通过这种方式,您的脚本与执行它的 "runtime environment" 隔离。
您可以在 ScalaClassLoader.asContext method, that you can see in your stack trace, that uses Thread.setContextClassLoader 找到一些详细信息,以将其自身设置为执行脚本的线程的主要 class加载程序。
Update(为什么它适用于 Mac 但不适用于 Windows)
*nix 的 scala
shell 脚本和 Windows 的 scala.bat
脚本之间的主要区别是默认情况下在 *nix 平台上标准 Scala 库被添加到引导Class路径(参见脚本中的 usebootcp
),而在 Windows 上,它们被添加到 "Usual Classpath"。这很重要,因为它定义了哪个 class 加载器将加载 scala.tools.nsc.MainGenericRunner
使用的 scala.reflect.internal.util.ScalaClassLoader
:它将是根 class 加载器(表示为 null
如果您调用 getClassLoader
) 或应用程序 Class 加载程序(即 sun.misc.Launcher$AppClassLoader
的实例)。这很重要,因为 CommonRunner.run
仅使用 urls
而没有 parent
ScalaClassLoader
的实例
def run(urls: Seq[URL], objectName: String, arguments: Seq[String]) {
(ScalaClassLoader fromURLs urls).run(objectName, arguments)
}
这意味着 "main" ScalaClassLoader
的父 class 加载程序将是引导 class 加载程序而不是 sun.misc.Launcher$AppClassLoader
因此当你问这个"main" ScalaClassLoader
对于 class scala.reflect.internal.util.ScalaClassLoader
它无法在其 class 加载程序链加载的 class 中找到它,因此必须加载它再次。这就是为什么您的脚本中有两个不同的 ScalaClassLoader
class 实例的原因。
有两个明显的解决方法(但都不太好):
- 更改 Scala 源代码中的
CommonRunner.run
以实际将当前上下文 class 加载器作为父级传递给新的ScalaClassLoader
(可能没那么容易 ☺) - 将
scala.bat
更改为对%_TOOL_CLASSPATH%
使用-Xbootclasspath/a:
而不是-cp
。但是查看 *nix 脚本中的usebootcp
我可以看到以下评论:
# 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
所以我怀疑如果您想对 REPL 使用 scala.bat
,将所有 Scala 库移动到 Boot Class 路径可能不是一个好主意。如果是这种情况,您可能需要创建 scala.bat
的副本(例如 scala_run_script.bat
)更改它并将其用于 运行 您的 Scala 脚本,留下标准 scala.bat
REPL.