Shaded Akka与Apache Toree版本冲突及配置文件问题排查
从错误日志来看,Toree自带的Akka 2.3.4中根本不存在akka.dispatch.BoundedControlAwareMessageQueueSemantics这个类——这个类是Akka 2.5.x版本才引入的。问题出在类路径上多个Akka的reference.conf被合并:当Toree启动时,它会加载自己的Akka 2.3.4配置,但你的uber-jar里的Akka 2.5.11的reference.conf也会被加载并合并进去,导致最终的配置里出现了只在高版本Akka中存在的mailbox配置项,而类路径里优先加载的是Toree的旧版本Akka类,自然找不到这个新类。
你可以通过sbt-assembly插件的shading功能,把你的项目依赖的Akka(包括Play WS间接依赖的部分)完全重命名到独立的命名空间,同时修改对应的reference.conf配置路径,让你的代码和Toree的Akka彻底隔离。具体步骤如下:
1. 配置SBT Assembly插件的Shade规则
在你的build.sbt中添加shade规则,将所有Akka相关的包重命名到自定义路径(比如my.shaded.akka):
// 先确保添加了sbt-assembly插件 addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") // 定义shade规则,重命名Akka及其相关依赖包 assemblyShadeRules in assembly := Seq( // 重命名核心Akka包 ShadeRule.rename("akka.**" -> "my.shaded.akka.@1") .inAll, // 重命名Typesafe旗下的Akka相关模块(比如Play依赖的部分) ShadeRule.rename("com.typesafe.akka.**" -> "my.shaded.com.typesafe.akka.@1") .inAll )
2. 处理reference.conf的合并与修改
Akka的配置是通过reference.conf加载的,我们需要合并所有配置文件,并将shaded后的Akka配置路径替换为新的命名空间:
assemblyMergeStrategy in assembly := { // 合并所有reference.conf,避免配置被覆盖 case PathList("reference.conf") => MergeStrategy.concat // 其他文件使用默认合并策略 case other => val defaultStrategy = (assemblyMergeStrategy in assembly).value defaultStrategy(other) } // 额外添加一个任务,修改shaded后的reference.conf中的配置路径 assemblyOption in assembly := (assemblyOption in assembly).value.copy( preAppendShellScript = None, // 替换所有配置中的akka.前缀为my.shaded.akka. transformAssembly = { asm => import java.io._ val temp = File.createTempFile("shaded-", ".jar") val jar = new java.util.jar.JarFile(asm) val out = new java.util.jar.JarOutputStream(new FileOutputStream(temp)) import scala.collection.JavaConverters._ jar.entries().asScala.foreach { entry => if (!entry.isDirectory) { out.putNextEntry(new java.util.jar.JarEntry(entry.getName)) if (entry.getName == "reference.conf") { // 读取配置内容并替换路径 val content = scala.io.Source.fromInputStream(jar.getInputStream(entry)).mkString val shadedContent = content.replaceAll("""^akka\.""", "my.shaded.akka.").replaceAll("""\bakka\.""", " my.shaded.akka.") out.write(shadedContent.getBytes) } else { // 其他文件直接复制 val buffer = new Array[Byte](1024) var len = jar.getInputStream(entry).read(buffer) while (len != -1) { out.write(buffer, 0, len) len = jar.getInputStream(entry).read(buffer) } } out.closeEntry() } } jar.close() out.close() temp } )
3. 调整代码中的Akka配置引用
如果你的代码直接创建了Akka的ActorSystem,需要指定使用shaded后的配置路径:
import my.shaded.akka.actor.ActorSystem import my.shaded.akka.util.Timeout import com.typesafe.config.ConfigFactory // 加载shaded后的Akka配置 val shadedConfig = ConfigFactory.load().getConfig("my.shaded.akka") val system = ActorSystem("my-shaded-system", shadedConfig)
如果是Play WS自动创建ActorSystem,需要在你的application.conf中配置它使用shaded的Akka:
play.ws.akka.actor-system = "my-shaded-ws-system" play.ws.akka.config = "my.shaded.akka"
为什么这样能解决问题?
通过shade重命名,你的项目依赖的Akka类和配置都被移动到了独立的my.shaded.akka命名空间下,和Toree自带的akka命名空间完全隔离:
- Toree会继续使用自己的Akka 2.3.4类和
akka.*配置 - 你的代码只会加载shaded后的Akka 2.5.11类和
my.shaded.akka.*配置
这样就不会出现配置合并导致的类找不到问题了。
内容的提问来源于stack exchange,提问作者mongolol

