以前、PlayをGraalVMで動かす記事を書いて、PlayがGuice(ランタイムDI)ベースなことに愚痴ってたら「コンパイルタイムDIもあるよ」とアドバイスを受けたので、試してみました。
やってみた
早速ですが、コード差分です。
仕事でPlayのバージョンアップ(2.3->2.4)をやっていて、そこでDIについて調べることがあったので、コードの修正についてはそこまでつまづくことがなかったです。基本的には下記の通り。
- Guice関連の依存ライブラリを
build.sbt
から外す。 -
@Inject
や@Singleton
など、ランタイムDI関連のアノテーションを外す。 - 独自のApplicationLoaderを作る。基本的には後述するApplicationComponentを作って
.application
を呼び出す実装にする。こうすると、テスト時にApplicationComponentを再利用できる。 - BuildInComponentsFromContextを継承した独自のApplicationComponentを作る。InjectedRoutesGeneratorによって作られたrouter.Routesクラスをインスタンス化するために必要なコントローラーをせっせと作る。MacWireを使わない場合、コンストラクタ引数の順序に注意する。
より詳しい方法はこちら。
感想
目論見通り、reflect-config.jsonからGuice関連のクラスの定義が減っていました。しかし、akkaやLogbackの内部でClass.forNameを多用しているようで、reflect-config.jsonを完全に無くすことはできませんでした。ただ、今回得られたreflect-config.jsonをPlay本体に加えることで、コンパイルタイムDIを採用している場合にGraalVM化する手間を多少は省けるのではないかなぁと考えています。※ApplicationLoaderだけはどうしてもreflect-config.jsonに追加する必要がある... Play本体を改修して、play.application.loaderのクラスから動的にreflect-config.jsonを作るのはありかもしれない。
Guice版もコンパイルタイムDI版も、結局は同じだけクラスをコンパイルする必要があるので、コンパイル時間は双方そんなに変わりませんでした。Guice版は依存関係をランタイムで解決するのでコンパイルが早いのが特徴ですが、GraalVMとしてビルドしてしまうとそのメリットがなくなってしまいますね。。
また、Guice版は、DI対象が増えるとreflect-config.jsonに追記が必要になりますが、コンパイルタイムDI版はその必要がありません。運用面を考えると、コンパイルタイムDI版の方が手軽かもしれません。
まとめ
PlayのコンパイルタイムDI版でGraalVM化してみました。必要なreflect-config.jsonが少なくなり、運用面でGuice版より有利になるので、GraalVM化するならコンパイルタイムDI版の方が良さそうです。
最近では、OpenJDKにはGraalVMが付いてくるらしいですし、QuarkusやSpring BoootはGraalVMオプションが標準で付いているので、Playもその波に乗れると嬉しいですね(マージしてほしい...)。