sbt 使用多个类加载器
sbt using multiple classloaders
Sbt 似乎使用了不同的 classloader,当在 sbt 会话中多次 运行 时,一些测试会失败,并出现以下错误:
[info] java.lang.ClassCastException: net.i2p.crypto.eddsa.EdDSAPublicKey cannot be cast to net.i2p.crypto.eddsa.EdDSAPublicKey
[info] at com.advancedtelematic.libtuf.crypt.EdcKeyPair$.generate(RsaKeyPair.scala:120)
使用模式匹配而不是 asInstanceOf
我得到了相同的结果。
如何确保 sbt 对同一会话中的所有测试执行使用相同的 class 加载器?
我认为这与此有关:Do security providers cause ClassLoader leaks in Java?。基本上 Security
是重新使用旧的 class-加载器的提供程序。所以这可能发生在任何多 class 路径环境(如 OSGi)中,而不仅仅是 SBT。
为您的 build.sbt
修复(不分叉):
testOptions in Test += Tests.Cleanup(() =>
java.security.Security.removeProvider("BC"))
实验:
sbt-classloader-issue$ sbt
> test
[success] Total time: 1 s, completed Jul 6, 2017 11:43:53 PM
> test
[success] Total time: 0 s, completed Jul 6, 2017 11:43:55 PM
解释:
从你的代码中可以看出(已发布here):
Security.addProvider(new BouncyCastleProvider)
您每次 运行 测试时都在重复使用相同的 BouncyCastleProvider
提供程序,因为您的 Security.addProvider
有效 only first time。由于 sbt 为每个 "test" 运行 创建新的 class-loader,但重新使用相同的 JVM - Security
是一种 JVM 范围的单例,因为它是由JVM-bootstrap,所以classOf[java.security.Security].getClassLoader() == null
和sbt不能reload/reinitialize这个class。
而且您可以轻松检查
classOf[org.bouncycastle.jce.spec.ECParameterSpec].getClassLoader()
res30: ClassLoader = URLClassLoader with NativeCopyLoader with RawResources
org.bouncycastle
classes 加载了自定义 classloader(来自 sbt),每次你 运行 test
.
所以这段代码:
val generator = KeyPairGenerator.getInstance("ECDSA", "BC")
获取从旧的 classloader 加载的 class 的实例(用于第一个 "test" 运行 的那个)并且你正在尝试使用规范初始化它新 class 装载机:
generator.initialize(ecSpec)
这就是您遇到 "parameter object not a ECParameterSpec" 异常的原因。 "net.i2p.crypto.eddsa.EdDSAPublicKey cannot be cast to net.i2p.crypto.eddsa.EdDSAPublicKey"的推理基本相同。
Sbt 似乎使用了不同的 classloader,当在 sbt 会话中多次 运行 时,一些测试会失败,并出现以下错误:
[info] java.lang.ClassCastException: net.i2p.crypto.eddsa.EdDSAPublicKey cannot be cast to net.i2p.crypto.eddsa.EdDSAPublicKey
[info] at com.advancedtelematic.libtuf.crypt.EdcKeyPair$.generate(RsaKeyPair.scala:120)
asInstanceOf
我得到了相同的结果。
如何确保 sbt 对同一会话中的所有测试执行使用相同的 class 加载器?
我认为这与此有关:Do security providers cause ClassLoader leaks in Java?。基本上 Security
是重新使用旧的 class-加载器的提供程序。所以这可能发生在任何多 class 路径环境(如 OSGi)中,而不仅仅是 SBT。
为您的 build.sbt
修复(不分叉):
testOptions in Test += Tests.Cleanup(() =>
java.security.Security.removeProvider("BC"))
实验:
sbt-classloader-issue$ sbt
> test
[success] Total time: 1 s, completed Jul 6, 2017 11:43:53 PM
> test
[success] Total time: 0 s, completed Jul 6, 2017 11:43:55 PM
解释:
从你的代码中可以看出(已发布here):
Security.addProvider(new BouncyCastleProvider)
您每次 运行 测试时都在重复使用相同的 BouncyCastleProvider
提供程序,因为您的 Security.addProvider
有效 only first time。由于 sbt 为每个 "test" 运行 创建新的 class-loader,但重新使用相同的 JVM - Security
是一种 JVM 范围的单例,因为它是由JVM-bootstrap,所以classOf[java.security.Security].getClassLoader() == null
和sbt不能reload/reinitialize这个class。
而且您可以轻松检查
classOf[org.bouncycastle.jce.spec.ECParameterSpec].getClassLoader()
res30: ClassLoader = URLClassLoader with NativeCopyLoader with RawResources
org.bouncycastle
classes 加载了自定义 classloader(来自 sbt),每次你 运行 test
.
所以这段代码:
val generator = KeyPairGenerator.getInstance("ECDSA", "BC")
获取从旧的 classloader 加载的 class 的实例(用于第一个 "test" 运行 的那个)并且你正在尝试使用规范初始化它新 class 装载机:
generator.initialize(ecSpec)
这就是您遇到 "parameter object not a ECParameterSpec" 异常的原因。 "net.i2p.crypto.eddsa.EdDSAPublicKey cannot be cast to net.i2p.crypto.eddsa.EdDSAPublicKey"的推理基本相同。