Play2.4のDIについて動作確認(Guiceの使い方)

  • 50
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

Play2.4でGuiceをつかったDIが出来るようになった。
簡単に使い方について動作確認を行った。

DIって?

そもそもDIって何よ?な場合はこちら。
newによって生成するような場合とFactoryメソッドパターンで生成する場合とDIで生成する場合とのコードの違いなどが例示されている。

https://github.com/google/guice/wiki/Motivation

動作確認

ベースコントローラの作成

先に activator new でmysampleという名前でテンプレートに基づいたアプリを作っておく。
今後、このコントローラを編集していく。

app/controllers/Application.scala
class Application extends Controller {
  def index = Action {
    Ok(views.html.index("Your new application is ready."))
  }
}

ControllerにServiceをInject

Serviceトレイトおよびその実装クラスを作成。servicesパッケージを作成し、その下に配置。

app/services/MyService.scala
trait MyService {
  def run(): String
}

class MyServiceImpl extends MyService {
  def run(): String = "MyServiceImpl!"
}

Controllerを修正。

app/controllers/Application.scala
class Application @Inject() (service: MyService) extends Controller {

  def index = Action {
    println(service.run())
    Ok(views.html.index("Your new application is ready."))
  }

}

続いてroutesを修正。
先頭に@を付与する。

conf/routes
GET     /                           @controllers.Application.index

MyServiceトレイトの実装クラスとして何を使うのかを宣言する。
modulesパッケージを作成し、AbstractModuleを継承したクラスをその下に配置。

modules/MyServiceModule.scala
class MyServiceModule extends AbstractModule {
  override def configure(): Unit = 
    bind(classOf[MyService]).to(classOf[MyServiceImpl])
}

最後に、application.confにmodulesの定義を追加。

conf/application.conf
play.modules.enabled += "modules.MyServiceModule"

http://localhost:9000 にアクセス。コンソールに MyServiceImpl! が表示されていることを確認。

なお、printlnするときに println(this) とかやるとわかりますが、アクセスが有る度にインスタンスが生成されていることが確認できます。
都度newされるのを抑制するには @Singleton アノテーションを付与することで実現できます。

app/controllers/Application.scala
@Singleton
class Application @Inject() (service: MyService) extends Controller {

アノテーションを使って同じことをする

上記と同様のことをアノテーションを使って実現する。

Serviceトレイトおよびその実装クラスは同じものを使用するが、traitに @ImplementedBy アノテーションを使ってこのトレイトの実装クラスを宣言する。

app/services/MyService.scala
@ImplementedBy(classOf[MyServiceImpl])
trait MyService {
  def run(): String
}

class MyServiceImpl extends MyService {
  def run() = "MyServiceImpl!"
}

先の例との違いは、以下2点となる。

  • MyServiceModule.scalaを必要としない
  • application.confに play.modules.enabled の追加も必要としない

実装クラスの切り替え(その1)

とはいえ、普通はトレイトを切るからには実装クラスは2つ以上無いと面白く無い。
MyService.scalaに実装クラスMyServiceImpl2を追加する。
なおこの時、先の例で追加した @Implemented アノテーションはMyServiceトレイトからは削除しておく。

app/services/MyService.scala
class MyServiceImpl2 extends MyService {
  def run() = "MyServiceImpl 2!"
}

改めてMyServiceModuleを定義する。

app/modules/MyServiceModule.scala
class MyServiceModule extends AbstractModule {
  override def configure(): Unit = {
    bind(classOf[MyService])
      .annotatedWith(Names.named("sono1"))
      .to(classOf[MyServiceImpl])

    bind(classOf[MyService])
      .annotatedWith(Names.named("sono2"))
      .to(classOf[MyServiceImpl2])
  }

で、Applicationコントローラを以下のように変える。
@Namedの引数をsono1やsono2に変えることで切り替え可能。

app/controllers/Application.scala
class Application @Inject() (@Named("sono2") service: MyService) extends Controller {

実装クラスの切り替えその2

テスト実施時と本番時で実装クラスを切り替える場合を想定する。

MyServiceトレイトをこのように変更する。

app/services/MyService.scala
@ProvidedBy(classOf[MyServiceProvider])
trait MyService {
  def run(): String
}

MyServiceProviderを実装する。
Environmentは特にModuleとかを用意しなくても標準で提供される。

app/providers/MyServiceProvider.scala
class MyServiceProvider @Inject() (env: Environment) extends Provider[MyService] {
  override def get(): MyService = {
    if (env.mode == Mode.Prod) new MyServiceImpl()
    else new MyServiceImpl2()
  }
}

以上。
Moduleの時のようにapplication.confへの追記は不要。
これで起動モードで実装クラスを切り替えることが出来た。

必ずしもtraitおよびその実装クラスを用意しなくてもよい

String型やInt型に具体的な値を適用する。

app/controllers/Application.scala
class Application @Inject() (@Named("sono1") service: MyService, @Named("num2") num: Int) extends Controller {

  def index = Action {
    println(service.run() * num)
    Ok(views.html.index("Your new application is ready."))
  }

}

新たなModuleを定義。

app/modules/MyNumModule.scala
class MyNumModule extends AbstractModule {
  override def configure(): Unit = {
    bind(classOf[Int])
      .annotatedWith(Names.named("num1"))
      .toInstance(1)

    bind(classOf[Int])
      .annotatedWith(Names.named("num2"))
      .toInstance(2)
  }
}

最後にapplication.confへ追記。

conf/application.conf
play.modules.enabled += "modules.MyServiceModule"
play.modules.enabled += "modules.MyNumModule"

これで実行すると、MyServiceImpl!が2回表示される。

参考資料