Play2(Java)でGuiceを使う
Play framework 2.x Java and 1.x Advent Calendar 2013 - Adventarの3日目のエントリです。
Play2は2.1からコントローラーがインスタンス化出来るようになったのでDIでサービクラスを注入したり出来るようになりました、みたいな事を何度か言ったような言わなかったような気がしますが、具体的に説明したことなった気がするのでちょっとサンプル書いてみたよ、というエントリです。*1
routes
Playは基本的にコントローラーをインスタンス化しないため、コントローラーをインスタンス化するためにはroutesファイルに特殊な定義をする必要があります。
特殊な、といっても別に大したことはなくて、頭に「@」を足すだけです。
GET /instantiate @controllers.Instantiate.index()
頭に「@」をつけて定義されたルーティングのコントローラーはインスタンス化されて起動します。
インスタンス化の方法はGlobalのgetControllerInstanceメソッドで定義することが出来ます。
Global
コントローラのインスタンスを具体的にどうやって構築するかはGlobalのgetControllerInstanceメソッドで支持することが出来ます。何もオーバーライドしなければコントローラのデフォルトコンストラクタが呼ばれます。
ここで、Guiceなどを用いてインスタンスを構築するように定義すれば、PlayでDIが使えるわけですね。
import com.google.inject.*; import models.*; import play.GlobalSettings; public class Global extends GlobalSettings { private static final Injector INJECTOR = Guice.createInjector(m1()); @Override public <A> A getControllerInstance(Class<A> controllerClass) throws Exception { return INJECTOR.getInstance(controllerClass); } private static Module m1() { // 固定文字列を返すリポジトリ return new AbstractModule() { @Override protected void configure() { bind(HogeService.class).to(HogeServiceImpl.class); bind(MessageRepository.class).to(MessageRepositoryImpl.class); } }; } private static Module m2() { // newされた時のタイムスタンプを返すリポジトリ return new AbstractModule() { @Override protected void configure() { bind(HogeService.class).to(HogeServiceImpl.class); bind(MessageRepository.class).to(MessageRepositoryImpl2.class); } }; } private static Module m3() { // newされた時のタイムスタンプを返すリポジトリ(ただしシングルトン) return new AbstractModule() { @Override protected void configure() { bind(HogeService.class).to(HogeServiceImpl.class); bind(MessageRepository.class).to(MessageRepositoryImpl2.class).in(Scopes.SINGLETON); } }; } }
コントローラとか
上のGlobalでもちょっと見えてますが、Serviceクラスのインタフェースと実装、サービスクラスはリポジトリからデータを取得して返し、リポジトリもインタフェースと実装がある、みたいな状況を想定しました。
// Instantiate.java package controllers; import com.google.inject.Inject; import models.HogeService; import play.mvc.Controller; import play.mvc.Result; public class Instantiate extends Controller { @Inject private HogeService hogeService; public Result index() { return ok(hogeService.getMessage()); } } //HogeService.java package models; public interface HogeService { public String getMessage(); } //HogeServiceImpl .java package models; import com.google.inject.Inject; public class HogeServiceImpl implements HogeService { @Inject private MessageRepository messageRepository; @Override public String getMessage() { return messageRepository.findMessage(); } } //MessageRepository.java package models; public interface MessageRepository { public String findMessage(); } //MessageRepositoryImpl.java package models; public class MessageRepositoryImpl implements MessageRepository { @Override public String findMessage() { return "ほげぇぇぇぇ"; } } //MessageRepositoryImpl2.java package models; public class MessageRepositoryImpl2 implements MessageRepository { private final String time = String.valueOf(System.currentTimeMillis()); @Override public String findMessage() { return time; } }
これで/instantiateにアクセスすると、m1()モジュールの場合常に固定の文字列が、m2()モジュールの場合アクセするたびに違うタイムスタンプが、m3()モジュールの場合最初にアクセスした時のタイムスタンプが表示され続けます。
と、こんな感じでPlayでもDI使えるよ、というエントリでした。*2
明日はtaise_515さんです。
ちなみに
この状態でroutesの「@」を外すとコンパイルエラーになります。
これはInstantiate#index()がstaticメンバでないためです。