scala - 如何在 SBT 任务中使用反射从源代码加载类?

标签 scala reflection sbt

我正在使用 SBT 来构建我的项目。我想在构建过程中使用 Scala 或 Java 反射来分析源代码中的一些类。

如何定义从源代码加载单个已知类或所有类的 SBT 任务?

import sbt._

val loadedClasses = taskKey[Seq[Class[_]]]("All classes from the source")

val classToLoad = settingKey[String]("Scala class name to load")
val loadedClass = taskKey[Seq[Class[_]]]("Loaded classToLoad")

最佳答案

您可以使用 fullClasspathAsJars SBT 任务的输出来访问从源代码生成的 JAR。此任务包含依赖项的 JAR。然后您可以创建一个 ClassLoader 来从这些 JAR 中加载类:

import java.net.URLClassLoader

val classLoader = taskKey[ClassLoader]("Class loader for source classes")
classLoader := {
  val jarUrls = (Compile / fullClasspathAsJars).value.map(_.data.toURI.toURL).toArray
  new URLClassLoader(jarUrls, ClassLoader.getSystemClassLoader)
}

然后,如果您知道 JAR 中类的名称,则可以使用此 ClassLoader 来加载它。

请注意 Scala 类名和 JAR 中的类名之间的区别。 Scala 类名可能会被破坏,并且一个 Scala 类可以在 JAR 中生成多个类。例如,以下代码片段中的 my.company.Box.MyClass 类会生成两个 JAR 类:my.company.Box$MyClassmy.company.Box$ MyClass$,后者是伴生对象的类。

package my.company
object Box {
  case class MyClass()
}

因此,如果您想通过 Scala 名称指定类或列出源中定义的所有类,则必须使用 compile SBT 任务的输出。此任务生成一个 CompileAnalysis 对象,该对象是内部 SBT API 的一部分,并且将来很容易发生更改。以下代码从 SBT 1.3.10 开始运行。

通过 Scala 名称加载类:

import sbt.internal.inc.Analysis
import xsbti.compile.CompileAnalysis

def loadClass(
  scalaClassName: String,
  classLoader: ClassLoader,
  compilation: CompileAnalysis
): List[Class[_]] = {
  compilation match {
    case analysis: Analysis =>
      analysis.relations.productClassName
        .forward(scalaClassName)
        .map(classLoader.loadClass)
        .toList
  }
}

classToLoad := "my.company.Box.MyClass"
loadedClass := loadClass(
  classToLoad.value,
  classLoader.value,
  (Compile / compile).value)

列出源代码中的所有类:

def loadAllClasses(
  classLoader: ClassLoader,
  compilation: CompileAnalysis,
): List[Class[_]] = {
  val fullClassNames = compilation match {
    case analysis: Analysis =>
      analysis.relations.allSources.flatMap { source =>
        // Scala class names
        val classNames = analysis.relations.classNames(source)
        val getProductName = analysis.relations.productClassName
        classNames.flatMap { className =>
          // Class names in the JAR
          val productNames = getProductName.forward(className)
          if (productNames.isEmpty) Set(className) else productNames
        }
      }.toList
  }

  fullClassNames.map(className => classLoader.loadClass(className))
}

loadedClasses := loadAllClasses(
  classLoader.value,
  (Compile / compile).value)

关于scala - 如何在 SBT 任务中使用反射从源代码加载类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62029478/

相关文章:

scala - 用 scala 替换列表中的元素

scala - 为什么选择带有散列而不是点的 Scala 类型成员?

java - 尝试建立一个可在多个地方使用的列表

scala - 在 Play 2.5 HTML 模板中自动注入(inject) WebJarAssets?

java - sbt 无法正确安装; Homebrew 程序更改Java版本

scala - Scala中如何读取38位精度18位小数的数据

scala - 从 Maven 逐渐切换到 SBT

java - 从 getDeclaredMethods 中查找具有实际类型的泛型方法

c# - 通过 System.Reflection 访问内部成员?

scala - 如何将sbt test/ScalaTest配置为仅显示失败?