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メンバでないためです。

*1:公式ドキュメントにも触りみたいなものは載ってるんですけどね

*2:公式にはSpring使った場合のサンプルもあります