技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

Play2.4 の DI を弄ってみる

まず適当に Singleton アノテーションをくっつけて使用する。

package utils.modules

import javax.inject._

/**
 * アクセスカウンター
 */
@Singleton
class AccessCounter {

  @volatile private var price = 0

  def add(p: Int) = price += p
  def get = price
}

そしたらコントローラでそれを Inject で利用する。

class Application @Inject() (accessCounter: AccessCounter) extends Controller {
  def index = Action {
    accessCounter.add(1)
    Ok(views.html.index(s"CurrentCount is ${accessCounter.get}."))
  }
}

凄まじく単純な使い方だ。

次は定義分離。

package utils.modules

import com.google.inject.ImplementedBy

@ImplementedBy(classOf[EnglishHello])
trait Hello {
  def sayHello(name: String): String
}

class EnglishHello extends Hello {
  def sayHello(name: String) = "Hello " + name
}

そして使ってみる。

class Application @Inject() (hello: Hello) extends Controller {
  def index = Action {
    Ok(views.html.index(s"CurrentCount is ${hello.sayHello("SOX!")}."))
  }
}

ネタに関しては突っ込まないで。
そして追加の DI を定義してみる。

追加定義。

class JaHello extends Hello {
  override def sayHello(name: String): String = "チョリーッス! " + name
}

これを使い分ける定義を記載する。

package utils.modules

import com.google.inject.AbstractModule
import com.google.inject.name.Names

class HelloModule extends AbstractModule {
  def configure() = {

    bind(classOf[Hello])
      .annotatedWith(Names.named("en"))
      .to(classOf[EnglishHello])

    bind(classOf[Hello])
      .annotatedWith(Names.named("ja"))
      .to(classOf[JaHello])
  }
}

そして設定を conf/application.conf で読み込む。

play.modules.enabled += "utils.modules.HelloModule"

これで使用準備 OK。
@Named アノテーションで日本語版の使用を指定する。

import javax.inject.{Named, Inject}
class Application @Inject() (@Named("ja") hello: Hello) extends Controller {
  def index = Action {
    Ok(views.html.index(s"CurrentCount is ${hello.sayHello("SOX!")}."))
  }
}

もう少しシンプルな定義が用意されているらしく、下記のようにも書ける。

import play.api.{Configuration, Environment}
import play.api.inject._

class HelloModule extends Module {
  def bindings(environment: Environment,
               configuration: Configuration) = Seq(
    bind[Hello].qualifiedWith("en").to[EnglishHello],
    bind[Hello].qualifiedWith("de").to[GermanHello]
  )
}

で、バインディングのデフォルト設定を書き換える場合、下記のように定義する。

import play.api.inject._

class HelloModule extends Module {
  def bindings(environment: Environment,
               configuration: Configuration) = Seq(
    bind[Hello].to[JaHello],
    bind[Hello].qualifiedWith("en").to[EnglishHello]
  )
}

こうすると、controller 側の @Named("ja") を引っこ抜いても日本語がデフォルトのバインディングになる。
それを踏まえた上で、 "Guice公式":https://github.com/google/guice/wiki/Scopes を眺めると、toProvider 構文があるのがわかる。

そしてよくよく思い出せば http://white-azalea.hatenablog.jp/entry/2015/08/23/180520 で貼り付けたリンク先。
「SecurityHeadersConfigProvider」何てものがあるじゃないか?

だが、現状ではうまく行ってない。
調べた所までメモ。

こんな定義を作って

@Singleton
class MySecurityHeadersConfigProvider @Inject() (configuration: Configuration) extends Provider[SecurityHeadersConfig] {
  lazy val get = SecurityHeadersConfig.fromConfiguration(
    configuration.++(Configuration("play" -> MySecurityHeadersConfigProvider.values)))
}

object MySecurityHeadersConfigProvider {
  val values = Map(
    "filters" -> Map(
      "headers" -> Map(
        "frameOptions" -> "SAMEORIGIN"
      )
    )
  )
}

こんなことをしてみたが、実行時エラー。
原因は、「A binding to play.filters.headers.SecurityHeadersConfig was already configured at play.filters.headers.SecurityHeadersModule.bindings(既にバインド設定されてるから設定できねーよ)」とのこと

class HelloModule extends Module {
  def bindings(environment: Environment,
               configuration: Configuration) = Seq(
    bind[SecurityHeadersConfig].toProvider[MySecurityHeadersConfigProvider],
    bind[Hello].to[JaHello],
    bind[Hello].qualifiedWith("en").to[EnglishHello]
  )
}

で、こんなものをを見つけたわけだが、

stackoverflow.com

ここだけ Module 分離してみたものの、取り込みで挫折。

class CustomApplicationLoader extends GuiceApplicationLoader() {
  override def builder(context: ApplicationLoader.Context): GuiceApplicationBuilder = {
    val extra = Configuration("a" -> 1)
    initialBuilder
      .in(context.environment)
      .loadConfig(extra ++ context.initialConfiguration)
      .overrides((overrides(context)): _*) // ←この辺に入れそうだけど、型定義が違う
  }
}

って所で、疲れたので寝る。

記事の最初から最後までのトータル時間 3 時間…流石に集中力切れたよ。