我正在使用 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$MyClass
和 my.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/