简体   繁体   English

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

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

I'm using SBT to build my project.我正在使用 SBT 来构建我的项目。 I want to analyze some classes from my source code using either Scala or Java reflection during the build process.我想在构建过程中使用 Scala 或 Java 反射从我的源代码中分析一些类。

How do I define an SBT task that loads a single known class or all classes from my source code?如何定义从我的源代码加载单个已知 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")

You can use the output of the fullClasspathAsJars SBT task to get access to the JARs produced from you source code.您可以使用fullClasspathAsJars SBT 任务的 output 来访问从您的源代码生成的 JARs。 This task doesn't include JARs of the dependencies.此任务包括依赖项的 JARs。 Then you can create a ClassLoader to load classes from those 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)
}

Then if you know the name of your class in the JAR, you can use this ClassLoader to load it.那么如果你知道JAR中你的class的名字,就可以使用这个ClassLoader来加载它。

Note the difference between Scala class names and class names in the JAR.注意 Scala class 名称和 Z529E625C8C2BF37C633Z4AAE495A1D0F8 中 class 名称之间的区别Scala class names may be mangled, and one Scala class can produce several classes 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. 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. 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.此任务生成一个CompileAnalysis分析 object,它是内部 SBT API 的一部分,并且在未来很容易发生变化。 The following code works as of SBT 1.3.10.以下代码适用于 SBT 1.3.10。

To load a class by its Scala name:通过 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)

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)

Based on Reference scala file from build.sbt add the following to project/build.sbt基于来自 build.sbt 的参考 scala 文件,将以下内容添加到project/build.sbt

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

and then scala-reflect on project's sources from within build.sbt like so然后从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])
}

where在哪里

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

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

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