Scalaを使ったプラガブルな実装

昔作ったScalaの言語構文とEvalの仕組みを利用した自作プラグインの作り方を紹介します。

Evalする部分については、こちらの記事を参考にさせて頂きました。
http://d.hatena.ne.jp/xuwei/20140607/1402128646

package parsers

import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
import java.io.File
import scala.io.Source

object Parser {
  def eval[T](code: String): T = {
    val toolbox = currentMirror.mkToolBox()
    val tree = toolbox.parse(code)
    toolbox.eval(tree).asInstanceOf[T]
  }

  def evalFile[T](file: File): T = {
    eval[T](Source.fromFile(file).getLines.mkString("\n"))
  }
} 

次に、Pluginの基底クラスを用意します。

package plugins

trait Plugin {
  def abstractMethod()
}

次は、Pluginの実装ファイル(PluginImpl.scala)を用意します。

new plugins.Plugin {  
  def abstractMethod() {
    println("hello")
  }
}

最後に、動的にファイルを実行してPluginをインスタンス化し、Plugin#abstractMethodを呼び出すメインとなるコードを実装します。

object Main {
  def main(args: Array[String]) {
    val pluginFile = new java.io.File("./PluginImpl.scala")
    val plugin = parsers.Parser.evalFile[plugins.Plugin](pluginFile)
    plugin.abstractMethod
  }
}

scalaファイルはコンパイルしてjarにし、PluginImpl.scala を同じ階層に配置して実行してみます。

$ java -jar Main.jar
hello

実際のシステムに実装する際には、特定のディレクトリ以下の.scalaファイルをすべて読んでインスタンス化したり、
一度読んだ後も、定期的にファイルのタイムスタンプをポーリングし、アプリケーションの再起動なしにプラグインをリロードできるようにしていました。
タイプセーフな設定ファイルなどもこの仕組みを使って実装し、システムを堅牢にするのに一役買っていました。