切换到Java 11后sbt测试报“No configuration setting found for key akka”
针对你遇到的问题——切换Java 11后sbt <module_name>/test跑测试时出现No configuration setting found for key 'akka',但打包Jar正常、IDEA内测试正常的情况,我整理了几个针对性的排查方向:
1. 验证测试阶段的资源复制与合并是否生效
你给reference.conf配置的concat MergeStrategy默认只作用于sbt assembly打包阶段,测试阶段的资源处理是sbt默认逻辑,可能没有合并多个模块的reference.conf。
你可以:
- 手动执行
sbt <module_name>/test:copyResources,然后到项目的target/scala-<你的版本>/test-classes目录下,检查是否存在reference.conf,并且文件内容是否包含所有模块的Akka配置(比如akka基础配置、自定义扩展配置等)。 - 如果发现测试类路径下的
reference.conf不完整,可以在build.sbt中为测试阶段单独添加资源合并逻辑:
Test / resourceGenerators += Def.task { // 收集编译阶段和测试阶段的所有reference.conf val confFiles = (Compile / resourceDirectory).value ** "reference.conf" ++ (Test / resourceDirectory).value ** "reference.conf" val targetFile = (Test / resourceManaged).value / "reference.conf" // 合并所有文件内容到目标文件 IO.concat(targetFile, confFiles.map(f => IO.readBytes(f))) Seq(targetFile) }.taskValue
2. 手动指定配置加载路径,绕过默认类加载逻辑
Java 11的模块系统(JPMS)可能改变了类加载器查找资源的方式,导致测试阶段默认的ConfigFactory.load()无法找到reference.conf。你可以在测试代码中手动指定配置文件的加载路径,验证是否是这个问题:
import com.typesafe.config.ConfigFactory import akka.actor.ActorSystem object MyObjectTest extends AsyncFunSuite { // 手动加载测试类路径下的reference.conf private val config = ConfigFactory.load("reference.conf") private implicit val system: ActorSystem = ActorSystem("MyActorSystem", config) private implicit val ex: ExecutionContext = system.dispatcher }
如果这样修改后测试能正常运行,说明默认的配置加载逻辑在Java 11的测试环境下存在问题,需要进一步排查类加载器的差异。
3. 检查CI环境与本地环境的sbt/Java差异
既然IDEA本地运行正常,CI失败,重点排查环境差异:
- sbt版本兼容性:确保CI使用的sbt版本支持Java 11(推荐sbt 1.3.0+,更稳妥的话用最新稳定版)。旧版sbt在Java 11下可能存在类加载或资源处理的bug。你可以在CI脚本中指定sbt版本,比如用
sbt-launcher或者cs install sbt:1.9.6来固定版本。 - Java环境完整性:CI里的Java 11是否是完整的JDK?部分精简版JRE可能缺少配置加载相关的组件,导致无法读取类路径下的资源文件。可以在CI中执行
java -version和javac -version确认是JDK环境。 - CI命令参数:检查CI中的sbt命令是否有额外参数(比如
-no-colors、-batch等),是否会影响资源加载?尝试在CI中直接运行sbt <module_name>/testOnly MyObjectTest,单独触发失败的测试,看是否能得到更详细的错误日志。
4. 开启配置加载的调试日志,定位问题根源
默认日志级别可能看不到配置加载的细节,你可以在测试类中临时调高日志级别,查看ConfigFactory加载reference.conf的全过程:
import org.slf4j.LoggerFactory import ch.qos.logback.classic.Level import com.typesafe.config.ConfigFactory import akka.actor.ActorSystem object MyObjectTest extends AsyncFunSuite { // 开启调试日志 val rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).asInstanceOf[ch.qos.logback.classic.Logger] rootLogger.setLevel(Level.DEBUG) // 加载配置并打印加载来源 private val config = ConfigFactory.load() println(s"Loaded config from: ${config.origin()}") private implicit val system: ActorSystem = ActorSystem("MyActorSystem", config) private implicit val ex: ExecutionContext = system.dispatcher }
运行测试后,查看日志中是否有配置文件的加载路径、是否存在加载失败的警告,这能帮你明确是找不到文件,还是文件内容缺失Akka配置。
5. 验证sbt测试类路径的实际内容
虽然你用sbt <module_name>/test:fullClasspath对比了迁移前后的类路径,但可以进一步验证类路径中是否包含正确的reference.conf:
- 在CI中执行
sbt <module_name>/test:printClasspath,把输出的类路径复制出来,逐个检查每个Jar/目录下是否有reference.conf,以及内容是否正确。 - 也可以用
sbt <module_name>/test:run运行一个简单的测试类,专门打印类路径下的资源:
import java.net.URLClassLoader object ResourceCheck extends App { val classLoader = Thread.currentThread().getContextClassLoader.asInstanceOf[URLClassLoader] val resource = classLoader.getResource("reference.conf") println(s"Found reference.conf at: ${resource}") if (resource != null) { val content = scala.io.Source.fromURL(resource).mkString println(s"Content preview:\n${content.take(500)}...") } else { println("reference.conf NOT found in classpath!") } }
在CI中运行sbt <module_name>/test:runMain ResourceCheck,就能直观看到测试阶段是否能找到reference.conf。
内容的提问来源于stack exchange,提问作者Mikalai Padkouka

