如何使用 SBT 任务内部的反射从源代码加载类? [英] How to load a class from the source code using reflection inside SBT task?

查看:32
本文介绍了如何使用 SBT 任务内部的反射从源代码加载类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 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$MyClassmy.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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆