如何使用 SBT 任务内部的反射从源代码加载类? [英] How to load a class from the source code using reflection inside SBT task?
问题描述
我正在使用 SBT 来构建我的项目.我想在构建过程中使用 Scala 或 Java 反射从我的源代码中分析一些类.
I'm using SBT to build my project. I want to analyze some classes from my source code using either Scala or Java reflection during the build process.
如何定义从我的源代码加载单个已知类或所有类的 SBT 任务?
How do I define an SBT task that loads a single known class or all classes from my source code?
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 加载类:
You can use the output of the fullClasspathAsJars
SBT task to get access to the JARs produced from you source code. This task doesn't include JARs of the dependencies. Then you can create a ClassLoader
to load classes from those JARs:
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
来加载它.
Then if you know the name of your class in the JAR, you can use this ClassLoader
to load it.
注意 Scala 类名和 JAR 中的类名之间的区别.Scala 类名可能会被修改,一个 Scala 类可以在 JAR 中生成多个类.例如,以下代码段中的 my.company.Box.MyClass
类生成两个 JAR 类:my.company.Box$MyClass
和 my.company.Box$MyClass$
,后者是伴随对象的类.
Note the difference between Scala class names and class names in the JAR. Scala class names may be mangled, and one Scala class can produce several classes in the JAR. For example my.company.Box.MyClass
class from the following snippet produces two JAR classes: my.company.Box$MyClass
and my.company.Box$MyClass$
, the latter being the class of the companion object.
package my.company
object Box {
case class MyClass()
}
<小时>
因此,如果您想通过 Scala 名称指定一个类或列出源代码中定义的所有类,则必须使用 compile
SBT 任务的输出.此任务会生成一个 CompileAnalysis
对象,该对象是内部 SBT API 的一部分,将来很容易发生变化.以下代码适用于 SBT 1.3.10.
So if you want to specify a class by its Scala name or to list all classes defined in the source, you have to use the output of the compile
SBT task. This task produces a CompileAnalysis
object which is part of internal SBT API and is prone to change in the future. The following code works as of SBT 1.3.10.
通过 Scala 名称加载类:
To load a class by its Scala name:
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)
要列出源代码中的所有类:
To list all classes from the source code:
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)
这篇关于如何使用 SBT 任务内部的反射从源代码加载类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!