もうそろそろ一区切りにしようと思っている、CDI勉強シリーズ。個人的に、次に勉強すべきJava EEのテーマはあるのですが…やるのでしょうか…。
で、今回はCDIのEventを使ってみたいと思います。
CDIで、デザインパターンのObserverパターンをサポートしているということらしいです。
Observerパターン
http://www.techscore.com/tech/DesignPattern/Observer.html/
CDIのEventについてメモ
http://qiita.com/opengl-8080/items/73bddc7c744624c6ef9c
自分も手を動かしてやってみましょう。
確認環境は、Weld SEとします。
準備
まずは、ビルド定義。
build.sbt
name := "cdi-event" version := "0.0.1-SNAPSHOT" scalaVersion := "2.11.6" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-feature") updateOptions := updateOptions.value.withCachedResolution(true) libraryDependencies ++= Seq( "org.jboss.weld.se" % "weld-se" % "2.2.12.Final", "org.scalatest" %% "scalatest" % "2.2.5" % "test" )
CDIを使うために、beans.xmlを用意します。
src/main/resources/META-INF/beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="annotated"> </beans>
また、テストコードを使って動作確認をしますが、Weldの起動/停止用にトレイトを用意しておきます。
src/test/scala/org/littlewings/javaee7/cdi/WeldSuiteSupport.scala
package org.littlewings.javaee7.cdi import org.jboss.weld.environment.se.Weld import org.scalatest.Suite trait WeldSuiteSupport extends Suite { protected def withWeld(f: => Unit): Unit = { val weld = new Weld try { weld.initialize() f } finally { weld.shutdown() } } }
テストコードでは、このトレイトをMix-inします。
はじめてのCDI Event
では、CDIのEventを使ったコードを書いていきます。登場するのは、この3つのクラスです。
- イベントの通知をする人(Subject)
- イベントの受け渡しに使われるもの
- イベントの通知を受け取る人(Observer)
それぞれ実装していきます。
まずは、イベントの受け渡しに使われるクラスを定義してみます。今回は、ログインイベントとしてみましょう。
src/main/scala/org/littlewings/javaee7/cdi/LoginEvent.scala
package org.littlewings.javaee7.cdi class LoginEvent(val firstName: String, val lastName: String)
これ自体は、何の変哲もないクラスです。
イベントを通知するSubject。CDI管理Beanとして作成し、Eventを@Injectしているところがポイントです。
src/main/scala/org/littlewings/javaee7/cdi/LoginSubject.scala
package org.littlewings.javaee7.cdi import javax.enterprise.context.Dependent import javax.enterprise.event.Event import javax.inject.Inject @Dependent class LoginSubject { @Inject private var loginEvent: Event[LoginEvent] = _ def login(firstName: String, lastName: String): Unit = { val event = new LoginEvent(firstName, lastName) loginEvent.fire(event) } }
イベントを発生させるには、Event#fireにイベントで受け渡すクラスを渡せばよいみたいです。
そしてObserver。Observerは複数いてもよさそうなので、今回は2つ用意してみました。
src/main/scala/org/littlewings/javaee7/cdi/LoginObserver.scala
package org.littlewings.javaee7.cdi import javax.enterprise.context.Dependent import javax.enterprise.event.Observes @Dependent class LoginObserver { def observe(@Observes event: LoginEvent): Unit = println(s"${event.lastName + event.firstName}さんがログインしました!!") } @Dependent class LoginObserver2 { def observe(@Observes event: LoginEvent): Unit = println(s"${event.lastName + event.firstName}さんがログインしたらしいですよ?") }
イベントを受け取るメソッドには、@Observesを付与すればよいみたいだと。
それでは、動作確認してみます。
src/test/scala/org/littlewings/javaee7/cdi/LoginEventSpec.scala
package org.littlewings.javaee7.cdi import javax.enterprise.inject.spi.CDI import org.scalatest.FunSpec class LoginEventSpec extends FunSpec with WeldSuiteSupport { describe("Login Event Spec") { // ここに、テストを書く! } }
テストと言っても、対象のコードがprintlnしかしてないので、ただ動かすだけですが…。
では、イベントを通知する人(Subject)のメソッドを起動します。
it("fire login event") { withWeld { val loginSubject = CDI.current.select(classOf[LoginSubject]).get loginSubject.login("カツオ", "磯野") } }
結果、このような出力が行われます。
磯野カツオさんがログインしました!! 磯野カツオさんがログインしたらしいですよ?
ちゃんと動いていますね。用意した、2つのObserverの両方が呼び出されています。
なお、BeanManager#fireEventでも同じことができるらしいです。
it("fire login event by BeanManager") { withWeld { val beanManager = CDI.current.getBeanManager val event = new LoginEvent("ワカメ", "磯野") beanManager.fireEvent(event) } }
結果。
磯野ワカメさんがログインしました!! 磯野ワカメさんがログインしたらしいですよ?
@Qualifierを併用する
続いて、@Qualifierを合わせて使うことで、通知する対象を絞ったりできるのだとか。こちらも試してみます。
先ほどと同じ定義の、イベントの受け渡しに使うクラス。
src/main/scala/org/littlewings/javaee7/cdi/qualifier/LoginEvent.scala
package org.littlewings.javaee7.cdi.qualifier class LoginEvent(val firstName: String, val lastName: String)
@Qualifierを用意します。@Adminと@Userという、2つの@Qualifierを用意しました。
src/main/java/org/littlewings/javaee7/cdi/qualifier/Admin.java
package org.littlewings.javaee7.cdi.qualifier; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PACKAGE, ElementType.TYPE}) public @interface Admin { }
@User
src/main/java/org/littlewings/javaee7/cdi/qualifier/User.java
package org.littlewings.javaee7.cdi.qualifier; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PACKAGE, ElementType.TYPE}) public @interface User { }
@Injectに@Qualifierを付与する
イベントを通知するSubject。
src/main/scala/org/littlewings/javaee7/cdi/qualifier/LoginSubject.scala
package org.littlewings.javaee7.cdi.qualifier import javax.enterprise.context.Dependent import javax.enterprise.event.Event import javax.inject.Inject @Dependent class LoginSubject { @Inject private var loginEvent: Event[LoginEvent] = _ def login(firstName: String, lastName: String): Unit = { val event = new LoginEvent(firstName, lastName) loginEvent.fire(event) } @User @Inject private var userLoginEvent: Event[LoginEvent] = _ def loginAsUser(firstName: String, lastName: String): Unit = { val event = new LoginEvent(firstName, lastName) userLoginEvent.fire(event) } @Admin @Inject private var adminLoginEvent: Event[LoginEvent] = _ def loginAsAdmin(firstName: String, lastName: String): Unit = { val event = new LoginEvent(firstName, lastName) adminLoginEvent.fire(event) } }
各Eventを@Injectする際に、先ほど作成した@Qualifierを付与しています(最初のは、何も付与していませんが)。
通知を受け取るObserver。
src/main/scala/org/littlewings/javaee7/cdi/qualifier/LoginObserver.scala
package org.littlewings.javaee7.cdi.qualifier import javax.enterprise.context.Dependent import javax.enterprise.event.Observes @Dependent class LoginObserver { def loggined(@Observes event: LoginEvent): Unit = println(s"${event.lastName + event.firstName}さんがログインしました!!") } @Dependent class AdminLoginObserver { def observe(@Observes @Admin event: LoginEvent): Unit = println(s"${event.lastName + event.firstName}さんが、管理者としてログインしました") } @Dependent class UserLoginObserver { def observe(@Observes @User event: LoginEvent): Unit = println(s"${event.lastName + event.firstName}さんが、一般ユーザーとしてログインしました") }
最初のObserverは@Observesのみですが、他のObserverは@Observersと先ほど作成した@Qualifierを使って受け取るイベントを絞り込みます。@Qualifierを付与しないと、同じ型のイベントを全部受け取るっぽいです。
確認してみましょう。
src/test/scala/org/littlewings/javaee7/cdi/qualifier/LoginEventSpec.scala
package org.littlewings.javaee7.cdi.qualifier import javax.enterprise.inject.spi.CDI import org.littlewings.javaee7.cdi.WeldSuiteSupport import org.scalatest.FunSpec class LoginEventSpec extends FunSpec with WeldSuiteSupport { // ここに、テストを書く!
@Qualifierなし。
it("no qualifier") { withWeld { val loginSubject = CDI.current.select(classOf[LoginSubject]).get loginSubject.login("カツオ", "磯野") } }
結果。
磯野カツオさんがログインしました!!
@Qualifierがないもののみ、イベントが通知されています。
@Userの@Qualifierを使用。
it("with @User qualifier") { withWeld { val loginSubject = CDI.current.select(classOf[LoginSubject]).get loginSubject.loginAsUser("マスオ", "フグ田") } }
結果。
フグ田マスオさんが、一般ユーザーとしてログインしました フグ田マスオさんがログインしました!!
@Userを付与したObserverと、何も付与していないObserverが動作しています。
@Admin指定。
it("with @Admin qualifier") { withWeld { val loginSubject = CDI.current.select(classOf[LoginSubject]).get loginSubject.loginAsAdmin("サザエ", "フグ田") } }
結果。
フグ田サザエさんがログインしました!! フグ田サザエさんが、管理者としてログインしました
@Userと似た結果に。そりゃあそうですね。
Event#fireを呼び出す時に絞り込む
続いて、指定する@Qualifierを@Injectではなく、Event#fireの時に決めるようにしてみましょう。
こちらが、そのコードになります。
src/main/scala/org/littlewings/javaee7/cdi/qualifier/LoginSubjectBySelect.scala
package org.littlewings.javaee7.cdi.qualifier import javax.enterprise.context.Dependent import javax.enterprise.event.Event import javax.enterprise.util.AnnotationLiteral import javax.inject.Inject @Dependent class LoginSubjectBySelect { @Inject private var loginEvent: Event[LoginEvent] = _ def login(firstName: String, lastName: String): Unit = { val event = new LoginEvent(firstName, lastName) loginEvent.fire(event) } def loginAsUser(firstName: String, lastName: String): Unit = { val event = new LoginEvent(firstName, lastName) loginEvent.select(new AnnotationLiteral[User] {}).fire(event) } def loginAsAdmin(firstName: String, lastName: String): Unit = { val event = new LoginEvent(firstName, lastName) loginEvent.select(classOf[LoginEvent], new AnnotationLiteral[Admin] {}).fire(event) } }
Event#selectで絞り込めるみたいです。
def loginAsUser(firstName: String, lastName: String): Unit = { val event = new LoginEvent(firstName, lastName) loginEvent.select(new AnnotationLiteral[User] {}).fire(event) }
また、select時に、Classクラスを渡せるのですが、これは@Inject時に型パラメーターに指定したクラスかそのサブクラスでなくてはいけないようです。今回は、意味はないですが…。
def loginAsAdmin(firstName: String, lastName: String): Unit = { val event = new LoginEvent(firstName, lastName) loginEvent.select(classOf[LoginEvent], new AnnotationLiteral[Admin] {}).fire(event) }
確認。
it("no qualifier") { withWeld { val loginSubject = CDI.current.select(classOf[LoginSubjectBySelect]).get loginSubject.login("カツオ", "磯野") } }
結果。
磯野カツオさんがログインしました!!
まあ、こちらは@Qualifierなしなので…。
@User指定。
it("with @User qualifier") { withWeld { val loginSubject = CDI.current.select(classOf[LoginSubjectBySelect]).get loginSubject.loginAsUser("マスオ", "フグ田") } }
結果。
フグ田マスオさんが、一般ユーザーとしてログインしました フグ田マスオさんがログインしました!!
@Injectの時と、同じ結果ですね。
@Admin指定。
it("with @Admin qualifier") { withWeld { val loginSubject = CDI.current.select(classOf[LoginSubjectBySelect]).get loginSubject.loginAsAdmin("サザエ", "フグ田") } }
結果。
フグ田サザエさんがログインしました!! フグ田サザエさんが、管理者としてログインしました
こちらも同様。
で
とりあえず使い方はわかりましたので、OKとしましょう。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/cdi-event