CDIのEventについてメモ

CDI の Event についてメモ。
要は、 Observer パターン的なものを CDI の機能を使って実現する方法のこと。

#環境
##Weld
1.1.10

#基本的な実装
##通知する人（Subject）
```java:Subject.java
package cdi.event.nullpo;

import javax.enterprise.event.Event;
import javax.inject.Inject;

public class Subject {
    
    @Inject private Event<NullPo> event;
    
    public void notifyObservers() {
        System.out.println("[Subject] ぬるぽ");
        this.event.fire(new NullPo());
    }
}
```

##通知を受ける人（Observer）
```java:ObserverA.java
package cdi.event.nullpo;

import javax.enterprise.event.Observes;

public class ObserverA {
    public void event(@Observes NullPo nullPo) {
        System.out.println("[Observer A] ｶﾞｯ!!");
    }
}
```

```java:ObserverB.java
package cdi.event.nullpo;

public class ObserverB {
    public void event(NullPo nullPo) {
        System.out.println("[Observer B] ｶﾞｯ!!");
    }
}
```

```java:ObserverC.java
package cdi.event.nullpo;

import java.util.List;
import javax.enterprise.event.Observes;

public class ObserverC {
    public void event(@Observes List<String> list) {
        System.out.println("[Observer C] ｶﾞｯ!!");
    }
}
```

##情報をやり取りするためのクラス
```java:NullPo.java
package cdi.event.nullpo;

public class NullPo {
}
```

##実行用クラス
```java:Main.java
package cdi.event.nullpo;

import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;

public class Main {
    public static void main(String[] args) {
        Weld weld = new Weld();
        WeldContainer container = weld.initialize();
        
        Subject application = container.instance().select(Subject.class).get();
        application.notifyObservers();
        
        weld.shutdown();
    }
}
```

##実行結果
```text:実行結果
[Subject] ぬるぽ
[Observer A] ｶﾞｯ!!
```

##説明
```java:イベントの通知
import javax.enterprise.event.Event;
import javax.inject.Inject;

public class Subject {
    
    @Inject private Event<NullPo> event; // Event オブジェクトを取得して
    
    public void notifyObservers() {
        this.event.fire(new NullPo()); // fire メソッドでイベントを通知
    }
}
```

イベントの通知は `javax.enterprise.event.Event` クラスの `fire` メソッドを使用する。 `Event` オブジェクト自体は、 `@Inject` を使ってインジェクションしておく。

`fire` メソッドの引数に渡したクラスを引数に受け取るメソッドで、かつその引数に `javax.enterprise.event.Observes` アノテーションが付与されている全てのメソッドにイベントが通知される。

```java:Observers
// Observer A
// NullPo を受け取り、 @Observer アノテーションが付与されているので通知対象
public void event(@Observes NullPo nullPo) {
    System.out.println("[Observer A] ｶﾞｯ!!");
}

// Observer B
// NullPo を受け取っているが、 @Observer アノテーションが付与されていないので、通知対象外
public void event(NullPo nullPo) {
    System.out.println("[Observer B] ｶﾞｯ!!");
}

// Observer C
// NullPo を受け取らないので、通知対象外
public void event(@Observes List<String> list) {
    System.out.println("[Observer C] ｶﾞｯ!!");
}
```

#カスタム限定子で通知対象を絞り込む
カスタム限定子を使って、通知対象を絞り込める。

##通知する人（Subject）
```java:Subject.java
package cdi.event.qualifier;

import javax.enterprise.event.Event;
import javax.inject.Inject;

public class Subject {
    
    @Inject @Man
    private Event<String> manEvent;
    
    @Inject @Woman
    private Event<String> womanEvent;
    
    public void notifyObservers() {
        
        System.out.println("[Subject] 男性挙手");
        this.manEvent.fire("notify");
        
        System.out.println("\r\n[Subject] 女性挙手");
        this.womanEvent.fire("nodify");
    }
}
```

##通知を受ける人（Observer）
```java:Taro.java
package cdi.event.qualifier;

import javax.enterprise.event.Observes;

public class Taro {
    public void event(@Observes @Man String info) {
        System.out.println("[太郎] ノ");
    }
}
```

```java:Hanako.java
package cdi.event.qualifier;

import javax.enterprise.event.Observes;

public class Hanako {
    public void event(@Observes @Woman String info) {
        System.out.println("[花子] ノ");
    }
}
```

```java:OsugiAndPiko
package cdi.event.qualifier;

import javax.enterprise.event.Observes;

public class OsugiAndPiko {
    public void event(@Observes String info) {
        System.out.println("[おすピー] ノ");
    }
}
```

##カスタム限定子
```java:Man.java
package cdi.event.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.PARAMETER, ElementType.FIELD})
public @interface Man {
}
```

```java:Woman.java
package cdi.event.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.PARAMETER, ElementType.FIELD})
public @interface Woman {
}
```

##実行用クラス
```java:Main.java
package cdi.event.qualifier;

import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;

public class Main {
    public static void main(String[] args) {
        Weld weld = new Weld();
        WeldContainer container = weld.initialize();
        
        Subject application = container.instance().select(Subject.class).get();
        application.notifyObservers();
        
        weld.shutdown();
    }
}
```

##実行結果
```text:実行結果
[Subject] 男性挙手
[太郎] ノ
[おすピー] ノ

[Subject] 女性挙手
[花子] ノ
[おすピー] ノ
```

##説明
```java:Eventにカスタム限定子を付与して、通知対象を絞り込む
    @Inject @Man
    private Event<String> manEvent;
    
    @Inject @Woman
    private Event<String> womanEvent;
```

`Event` をインジェクションする場所でカスタム限定子を付与することで、そのイベントの通知先を絞り込むことが出来る。

カスタム限定子で絞りこまれたイベントは、 `@Observers` アノテーションのみ付与されているか、または `@Observers` アノテーションとカスタム限定子が両方ともパラメータに付与されているメソッドに通知される。

```java:Observers
// Taro
// @Man が付与された Event だけが通知される
public void event(@Observes @Man String info) {
    System.out.println("[太郎] ノ");
}

// Hanako
// @Woman が付与された Event だけが通知される
public void event(@Observes @Woman String info) {
    System.out.println("[花子] ノ");
}


// OsugiAndPiko
// カスタム限定子が付与されていないので、全ての Event が通知される
public void event(@Observes String info) {
    System.out.println("[おすピー] ノ");
}
```

ちなみに、 `@Man` と `@Woman` の両方が付与されていると、逆にイベントは通知されない。

```java:
public void event(@Observes @Man @Woman String info) {
    System.out.println("ノ");
}
```

##カスタム限定子を動的に指定する
前述の方法だと、イベントの絞り込みが静的になり、条件によって通知する対象を切り替えるような動的な処理ができない。

動的に通知対象を絞り込みたい場合は以下のようにする。

```java:動的に通知対象を絞り込む

    @Inject
    private Event<String> event;
    
    @SuppressWarnings("serial")
    public void notifyObservers() {
        this.event.select(new AnnotationLiteral<Man>() {}).fire("notify");
    }
```

`Event#select(Annotation...)` メソッドで絞込対象を指定して、続けて `fire` メソッドを実行する。


#例外処理について
Observer 側のいずれかの処理で例外が発生した場合は、 `fire` メソッドで例外をキャッチできる。

```java:Main.java
package cdi.event.test;

import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.inject.Inject;

import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;

public class Main {
    public static void main(String[] args) {
        Weld weld = new Weld();
        WeldContainer container = weld.initialize();
        
        Main main = container.instance().select(Main.class).get();
        main.notifyObservers();    // １
        
        weld.shutdown();
    }
    
    @Inject
    private Event<String> event;
    
    public void notifyObservers() {
        try {
            this.event.fire("notify");    // ２
        } catch (Exception e) {
            System.out.println(e.getMessage());    // ４
        }
    }
    
    public void event(@Observes String info) {
        throw new RuntimeException("error!!");    // ３
    }
}
```

```text:実行結果
error!!
```


#参考
+ [Chapter 11. Events | Seam Framework - Seam Documentation](http://docs.jboss.org/weld/reference/latest/en-US/html/events.html)