如何在运行时从外部文件编译代码



我想设计一个Scala程序,它接受Scala文件作为参数,可以自定义程序的执行。特别是,我想在运行时提供包含将由程序调用的方法的实现的文件。如何正确地依赖外部文件并在运行时动态调用它们的方法?理想情况下,我也希望这些文件能够依赖于我的程序中的方法和类。

示例场景:我有一个包含val p: Plant = Greenhouse.getPlant()行的函数,并且在运行时提供的一个文件中定义了Greenhouse类和getPlant方法。在该文件中,方法getPlant返回Rose,其中Rose <: PlantPlant是在原始程序中定义的。假设文件只在运行时而不是在编译时连接,我如何实现(或近似)这种相互依赖性?

下面是如何仅使用标准Scala实现的。不明显的东西都在GreenhouseFactory:

package customizable
abstract class Plant
case class Rose() extends Plant
abstract class Greenhouse {
  def getPlant(): Plant
}
case class GreenhouseFactory(implFilename: String) {
  import reflect.runtime.currentMirror
  import tools.reflect.ToolBox
  val toolbox = currentMirror.mkToolBox()
  import toolbox.u._
  import io.Source
  val fileContents = Source.fromFile(implFilename).getLines.mkString("n")
  val tree = toolbox.parse("import customizable._; " + fileContents)
  val compiledCode = toolbox.compile(tree)
  def make(): Greenhouse = compiledCode().asInstanceOf[Greenhouse]
}
object Main {
  def main(args: Array[String]) {
    val greenhouseFactory = GreenhouseFactory("external.scala")
    val greenhouse = greenhouseFactory.make()
    val p = greenhouse.getPlant()
    println(p)
  }
}

把你的重写表达式放在external.scala:

new Greenhouse {
  override def getPlant() = new Rose()
}

输出为:

Rose()
唯一棘手的事情是GreenhouseFactory需要在import语句之前提供对外部文件所需的所有类型和符号的访问。为方便起见,将所有这些东西制作成一个包。

编译器ToolBox在这里有文档说明。除了奇怪的导入之外,您真正需要知道的唯一一件事是,toolbox.parse将字符串(Scala源代码)转换为抽象语法树,而toolbox.compile将抽象语法树转换为签名为() => Any的函数。由于这是动态编译的代码,因此必须将Any强制转换为您期望的类型。

Scala本身不提供这种功能。我所知道的最简单的方法是使用Twitter的"util-eval"库。这个库封装了对Scala编译器的必要调用和各种类加载仪式,为您节省了大量的工作。执行您所描述的操作的调用序列看起来类似于

val eval = new Eval()
val greenhouse = eval.apply[Greenhouse](new File("path/to/MyGreenhouse.scala"))
val plant = greenhouse.getPlant()

动态加载的Scala文件需要包含一个表达式,而不是一个类本身,但这很容易做到,基本上就像这样。

new Greenhouse{
    def getPlant() = //thing to return the plant 
}

据我所知,Twitter使用(或至少使用)这个功能将其配置文件作为Scala而不是properties/json/xml。

相关内容

  • 没有找到相关文章

最新更新