sbt使用多个类加载器

时间:2017-06-29 17:06:36

标签: scala sbt classloader

Sbt似乎正在使用不同的类加载器,在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)

I tried equivalent code使用模式匹配而不是asInstanceOf,我得到相同的结果。

如何确保sbt在同一会话中为所有测试执行使用相同的类加载器?

1 个答案:

答案 0 :(得分:1)

我认为这与此有关:Do security providers cause ClassLoader leaks in Java?。基本上Security正在重复使用旧类加载器中的提供程序。所以这可能发生在任何多类路径环境(如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为每次“测试”运行创建了新的类加载器,但是重新使用相同的JVM - Security是由JVM-bootstrap加载的JVM范围的单例,因此classOf[java.security.Security].getClassLoader() == null并且sbt无法重新加载/重新初始化此类。

你可以轻松检查

classOf[org.bouncycastle.jce.spec.ECParameterSpec].getClassLoader()
res30: ClassLoader = URLClassLoader with NativeCopyLoader with RawResources

org.bouncycastle个类加载了自定义类加载器(来自sbt),每次运行时都会更改test

所以这段代码:

val generator = KeyPairGenerator.getInstance("ECDSA", "BC")

获取从旧类加载器加载的类的实例(用于第一次“测试”运行的类)并且您尝试使用新类加载器的规范初始化它:

generator.initialize(ecSpec)

这就是为什么你得到“参数对象而不是ECParameterSpec”的例外。 “net.i2p.crypto.eddsa.EdDSAPublicKey无法转换为net.i2p.crypto.eddsa.EdDSAPublicKey”的推理基本相同。