繁体   English   中英

如何使用 SBT 任务中的反射从源代码加载 class?

[英]How to load a class from the source code using reflection inside SBT task?

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

如何定义从我的源代码加载单个已知 class 或所有类的 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 任务的 output 来访问从您的源代码生成的 JARs。 此任务包括依赖项的 JARs。 然后你可以创建一个ClassLoader来从那些 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中你的class的名字,就可以使用这个ClassLoader来加载它。

注意 Scala class 名称和 Z529E625C8C2BF37C633Z4AAE495A1D0F8 中 class 名称之间的区别 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()
}

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. 此任务生成一个CompileAnalysis分析 object,它是内部 SBT API 的一部分,并且在未来很容易发生变化。 以下代码适用于 SBT 1.3.10。

通过 Scala 名称加载 class:

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)

基于来自 build.sbt 的参考 scala 文件,将以下内容添加到project/build.sbt

Compile / unmanagedSourceDirectories += baseDirectory.value / ".." / "src" / "main" / "scala"

然后从build.sbt中对项目的源进行scala-reflect ,就像这样

val reflectScalaClasses = taskKey[Unit]("Reflect on project sources from within sbt")
reflectScalaClasses := {
  import scala.reflect.runtime.universe._
  println(typeOf[example.Hello])
}

在哪里

src
├── main
│   └── scala
│       └── example
│           ├── Hello.scala

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM