在 SBT 中为模块依赖使用自定义 class 加载器

Using a custom class loader for a module dependency in SBT

我有一个由 apicorethird-party 组成的多模块 SBT 构建。结构大致是这样的:

api
|- core
|- third-party

third-party 的代码实现了 api 并且是从其他地方逐字复制的,所以我真的不想碰它。

由于 third-party 的实现方式(大量使用单例),我不能只让 core 依赖于 third-party。具体来说,我只需要通过 api 使用它,但我需要在运行时拥有 third-party 的多个独立副本。 (这允许我同时拥有多个 "singletons"。)

如果我 运行 在我的 SBT 构建之外,我会这样做:

def createInstance(): foo.bar.API = {
  val loader = new java.net.URLClassLoader("path/to/third-party.jar", parent)
  loader.loadClass("foo.bar.Impl").asSubclass(classOf[foo.bar.API]).newInstance()
}

但问题是,如果我是 运行 通过 sbt core/run,我不知道如何在运行时弄清楚我应该给 URLClassLoader 什么作为参数。

这应该可行,尽管我没有用您的设置对其进行完全测试。

基本思路是让sbt将类路径写入一个文件 可以在运行时使用。 sbt-buildinfo 已经为此提供了一个很好的基础,所以我要在这里使用它,但是你 可能只提取相关部分而不使用此插件。

将此添加到您的项目定义中:

lazy val core = project enablePlugins BuildInfoPlugin settings (
  buildInfoKeys := Seq(BuildInfoKey.map(exportedProducts in (`third-party`, Runtime)) {
    case (_, classFiles) ⇒ ("thirdParty", classFiles.map(_.data.toURI.toURL)) 
  })
  ...

在运行时,使用这个:

def createInstance(): foo.bar.API = {
  val loader = new java.net.URLClassLoader(buildinfo.BuildInfo.thirdParty.toArray, parent)
  loader.loadClass("foo.bar.Impl").asSubclass(classOf[foo.bar.API]).newInstance()
}

exportedProducts 仅包含项目的已编译 类(例如 .../target/scala-2.10/classes/)。根据您的设置,您可能希望使用 fullClasspath (其中还包含 libraryDependencies 和依赖项目)或任何其他与类路径相关的键。