はじめに
Play2.4でGuiceをつかったDIが出来るようになった。
簡単に使い方について動作確認を行った。
DIって?
そもそもDIって何よ?な場合はこちら。
newによって生成するような場合とFactoryメソッドパターンで生成する場合とDIで生成する場合とのコードの違いなどが例示されている。
動作確認
ベースコントローラの作成
先に activator new
でmysampleという名前でテンプレートに基づいたアプリを作っておく。
今後、このコントローラを編集していく。
class Application extends Controller {
def index = Action {
Ok(views.html.index("Your new application is ready."))
}
}
ControllerにServiceをInject
Serviceトレイトおよびその実装クラスを作成。servicesパッケージを作成し、その下に配置。
trait MyService {
def run(): String
}
class MyServiceImpl extends MyService {
def run(): String = "MyServiceImpl!"
}
Controllerを修正。
class Application @Inject() (service: MyService) extends Controller {
def index = Action {
println(service.run())
Ok(views.html.index("Your new application is ready."))
}
}
続いてroutesを修正。
先頭に@を付与する。
GET / @controllers.Application.index
MyServiceトレイトの実装クラスとして何を使うのかを宣言する。
modulesパッケージを作成し、AbstractModuleを継承したクラスをその下に配置。
class MyServiceModule extends AbstractModule {
override def configure(): Unit =
bind(classOf[MyService]).to(classOf[MyServiceImpl])
}
最後に、application.confにmodulesの定義を追加。
play.modules.enabled += "modules.MyServiceModule"
http://localhost:9000
にアクセス。コンソールに MyServiceImpl!
が表示されていることを確認。
なお、printlnするときに println(this)
とかやるとわかりますが、アクセスが有る度にインスタンスが生成されていることが確認できます。
都度newされるのを抑制するには @Singleton
アノテーションを付与することで実現できます。
@Singleton
class Application @Inject() (service: MyService) extends Controller {
アノテーションを使って同じことをする
上記と同様のことをアノテーションを使って実現する。
Serviceトレイトおよびその実装クラスは同じものを使用するが、traitに @ImplementedBy
アノテーションを使ってこのトレイトの実装クラスを宣言する。
@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トレイトからは削除しておく。
class MyServiceImpl2 extends MyService {
def run() = "MyServiceImpl 2!"
}
改めてMyServiceModuleを定義する。
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に変えることで切り替え可能。
class Application @Inject() (@Named("sono2") service: MyService) extends Controller {
実装クラスの切り替えその2
テスト実施時と本番時で実装クラスを切り替える場合を想定する。
MyServiceトレイトをこのように変更する。
@ProvidedBy(classOf[MyServiceProvider])
trait MyService {
def run(): String
}
MyServiceProviderを実装する。
Environmentは特にModuleとかを用意しなくても標準で提供される。
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型に具体的な値を適用する。
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を定義。
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へ追記。
play.modules.enabled += "modules.MyServiceModule"
play.modules.enabled += "modules.MyNumModule"
これで実行すると、MyServiceImpl!が2回表示される。