• 検索結果がありません。

第 13 章 CDIの高度な機能 この章では CDIビーンが持つ便利な機能の数々を一挙に紹介します 開発実務で は欠かせない機能です 中でも メソッドの開始前後に割り込んでログを取ったりで きるインターセプタは特に有用です その他 コンストラクタの直後に実行する処理 を定義できるライフサイクルコール

N/A
N/A
Protected

Academic year: 2021

シェア "第 13 章 CDIの高度な機能 この章では CDIビーンが持つ便利な機能の数々を一挙に紹介します 開発実務で は欠かせない機能です 中でも メソッドの開始前後に割り込んでログを取ったりで きるインターセプタは特に有用です その他 コンストラクタの直後に実行する処理 を定義できるライフサイクルコール"

Copied!
26
0
0

読み込み中.... (全文を見る)

全文

(1)

13

CDIの高度な機能

 この章では、CDIビーンが持つ便利な機能の数々を一挙に紹介します。開発実務で は欠かせない機能です。中でも、メソッドの開始前後に割り込んでログを取ったりで きるインターセプタは特に有用です。その他、コンストラクタの直後に実行する処理 を定義できるライフサイクルコールバックメソッド、既存のメソッドの機能を外部か ら変更できるデコレーター、イベントを発生させて、それに対応する処理を作成し、 コンカレントに実行できるオブザーバーなど、楽しい機能が満載の章です。 1.コールバックメソッド: 初期化と終了処理 2.インターセプタ: 割り込み処理 3.デコレーター: メソッドの機能を変更する 4.オブザーバー: イベント駆動処理 5.まとめ 1 コールバックメソッド ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 2 インターセプタ(割り込み処理) ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 3 デコレーター ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 4 オブザーバー(イベント駆動処理) ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 5 まとめ ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 他の章と同じスタイルにしてください (指示の間違いでした) ◎ほかの章も含めて全体にかかること 「まとめ」と「練習問題」は本文よりも1ポイント小さなフォントを使ってください

(2)

13

Chapter13 CDIの高度な機能  CDIはコンストラクタを実行した直後と、オブジェクトを廃棄する直前のタイミングを とらえて、作成しておいた特定のメソッドを実行させることができます。オブジェクトの ライフサイクルに連動して動作するこのようなメソッドを、ライフサイクル・コールバッ クメソッドといいます。

1.1 ライフサイクル・コールバックメソッド

 ライフサイクル・コールバックメソッドは、対象とするCDIビーンの中に書きます。メソッ ドに次の表に示すアノテーションを付けるだけで作成できます。 コールバックメソッドのアノテーション アノテーション 実行するタイミング @PostConstruct コンストラクタを実行し、全てのインジェクション完了直後 @PreDestroy オブジェクトを廃棄する直前  ただし、コールバックメソッドは戻り値や引数を持つことができないので注意してくだ さい。次が作成の要件となっています。 ・戻り値はvoid ・引数なし ・例外はスローできない ・アクセス修飾子としてstatic、finalは使用できない ・必要な場合は、複数のアノテーションを付けてよい Section Section Section Section Section S Sectition Se Sectctioionn Section S i S ti

1

コールバックメソッド: 初期化と終了処理

(3)

13

コールバックメソッド sample18-01/beans/Bb.java 例題1 @Named @RequestScoped public class Bb {

private Integer number; private String name; { System.out.println("①初期化ブロック"); } public Bb() { System.out.println("②コンストラクタ/"); } @PostConstruct public void start() {

System.out.println("③PostConstruct"); }

@PreDestroy

public void end() {

System.out.println("④PostDestroy"); }

// セッター、ゲッター(省略) }

 例題では

@PostConstruct

を付けた

start

メソッドと、

@PreDestroy

を付けた

end

ソッドを定義しています。sample18-01を開いて実行してみてください。実行すると画面 が表示され、同時にサーバーログに次のように①~④のメッセージが表示されます。

 ①は初期化ブロックでの出力で、②はコンストラクタでの出力です。①と②が実行さ れ完全にオブジェクト(バッキングビーン)の構築が終わった後、

@PostConstruct

start

メソッドが実行され③が表示されます。

(4)

13

Chapter13 CDIの高度な機能

 バッキングビーン(

Bb.java

)は

@RequestScoped

ですから、初めてsample18-01を実

行した時は画面を表示するとすぐに廃棄されます。④は画面を表示した後、オブジェクト を廃棄する直前に

end

メソッドが表示したメッセージです。

JSFのライフサイクルとの関係

 JSFのライフサイクルとは、ブラウザからサーバーにリクエストが届いてから、サーバー がレスポンスを返すまでの間にJSFシステムが実行する一連の処理の流れです。それは次 のように6つのフェーズから構成されていました(4章1節「JSFの仕組み」参照)。 1. [ビューの復元]コンポーネントツリーを復元 2. [入力値の適用]入力値をコンポーネントにセットする 3. [変換と検証]入力値を適切な型に型変換し、指定された検証を行う 4. [モデル値の更新]コンポーネントの値でバッキングビーンの変数を更新する 5. [アプリケーションの呼び出し]コマンドボタンなどで指定されたメソッドを実行する 6. [レスポンスのレンダリング]表示する画面を作成してブラウザに送信する  初めてsample18-01を実行した時は、画面を表示するだけですから、1. ~ 5.はなく、サー バーログ①~③は、6.[レスポンスのレンダリング]フェーズの最初に出力されます。④は このフェーズが完了した後、バッキングビーンを廃棄する直前に出力されます。  また、画面に番号と名前を適当に入力して送信ボタンを押した時は、①~③は3.[変換と 検証]フェーズの最初に出力されます。この時点でバッキングビーンはすでに構築されて いるわけです。④は6.の後で変わりません。

(5)

13

 インターセプタは、メソッドやコンストラクタの処理の前後に割り込んで、何かの処理 を実行するプログラムです。例えば、クラスのすべてのメソッドに対して、実行の直前に 割り込んでログを取る、などが代表的な機能です。  インターセプタは、1つだけ作成しておくと、あらゆるメソッド・コンストラクタにインジェ クトできます。ただし、変数ではなく、クラスやメソッド、コンストラクタに対して、インジェ クトするので、@Injectは使えません。代わりに"カスタムアノテーション"を使います。  カスタムアノテーションをクラスやメソッドに書いておくと、それだけで、特定のイン ターセプタがインジェクトされます。このように、カスタムアノテーションを使って特定 のインターセプタをCDIに紐づけることを、インターセプタ・バインディングといいます。  そこで、インターセプタを利用するには、インターセプタ本体と共に、カスタムアノテー ションを作る必要があります。

2.1 カスタムアノテーションの作成

 インターセプタの種類ごとに、カスタムアノテーションが必要です。まず、カスタムア ノテーションから作っておきましょう。  ここでは、割り込み先でログを取るインターセプタを作るので、カスタムアノテーショ ンは、@Loggableにします。NetBeansでは、名前を指定するだけで作成できます。 【作成手順】 ①プロジェクトを右ボタンでクリックし、[新規]⇒[その他]と選択する  ⇒新規ファイルダイアログが開く ② カテゴリ欄で[コンテキストと依存性の注入]、ファイルタイプ欄で[インターセプタ結合型] を選んで[次>]ボタンを押す ⇒ ダイアログが開く ③ダイアログの[クラス名]にLoggableと入力して[終了]を押す  以上で、次のようなカスタムアノテーションの定義が生成されます。 Section Section Section Section Section S Sectition Se Sectctioionn Section S i S ti

2

インターセプター: 割り込み処理

(6)

13

Chapter13 CDIの高度な機能 カスタムアノテーション sample18-02/beans/Loggable.java 例題2 1 @Inherited 2 @InterceptorBinding 3 @Retention(RUNTIME) 4 @Target({METHOD, TYPE})

5 public @interface Loggable {

6 }

2.2 インターセプタを作成する

 インターセプタは普通のクラスとして作成します。ただし、クラスには、インターセプ タであることを示す@Interceptorと、作成したカスタムアノテーション(@Loggable)を付 けて宣言します。これにより、インターセプタとCDIの紐づけができるわけです。 @Interceptor @Loggable

public class LoggingInteceptor implements Serializable { ・・・ }  インターセプタが@RequestScopedのCDIビーンでだけ利用されるなら、Serializableの 実装は不要です。しかし、@SessionScopedや@Conversationなどのスコープを持つCDIビー ンでも使われるなら、これを付けておかないと実行時エラーになります。おおむね、付け ておいたほうが無難です。  次に、インターセプタのメソッドは、コンストラクタ用とメソッド用で異なるアノテーショ ンを付けます。 // コンストラクタ用 @AroundConstruct

public void ConstructorLogging(InvocationContext ic) throws Exception {

・・・ }

// メソッド用 @AroundInvoke

public void MethodLogging(InvocationContext ic) throws Exception { ・・・

}

(7)

13

 コンストラクタ用には、@AroundConstruct、メソッド用には、@AroundInvokeを付け ます。どちらも、実行時の情報を持つInvocationContextを引数にとります。また、例外処 理はしないので、throws Exceptionを付けて宣言します。  次の例題で詳しく見てみましょう。 例題2(続き)インターセプタ sample18-02/beans/LoggingInterceptor.java 1 @Interceptor 2 @Loggable

3 public class LoggingInterceptor implements Serializable {

4 private static final long serialVersionUID = 1L;

5 @Inject transient private Logger log; 6

7 @AroundConstruct

8 public void ConstructorLogging(InvocationContext ic) throws Exception {

9 log.fine("◇ENTRY:..." + ic.getConstructor().getName());

10 try {

11 ic.proceed();

12 } finally {

13 log.fine("◇EXIT:..." + ic.getConstructor().getName());

14 }

15 }

16 @AroundInvoke

17 public Object MethodLogging(InvocationContext ic) throws Exception {

18 String className = ic.getMethod().getDeclaringClass().getName();

19 String methodName = ic.getMethod().getName();

20 String paramList = Arrays.toString(ic.getParameters()); 21

22 log.fine("⃝ENTRY:" +methodName + "..."+paramList + "..." + className);

23 Object result = null;

24 try {

25 result = ic.proceed();

26 return result;

27 } finally {

28 log.fine("⃝RETURN:" + methodName + "..."+result + "..." + className);

29 } 30 } 31 }  まず、コンストラクタ用のインターセプタから見ていきましょう。  8行目の ConstructorLoggingがコンストラクタ用のインターセプタメソッドです。

コンストラクタ用のインターセプタでの処理

  処理は次の3つです 青字にする 下線と枠線が重ならないように調整 // 12章4.4節で作成したロガー コメントを追加

(8)

13

Chapter13 CDIの高度な機能    ①ログを記録する<9行目>     ・ic.getConstructor().getName()は、"コンストラクタ名"を取得する書き方です    ②コンストラクタを実行する<11行目>     ・ic.proceed(); は、コンストラクタを実行する命令です    ③ログを記録する<13行目>  try文になっているのは、ic.proceed();でコンストラクタを実行すると例外を発生する可 能性があるからですが、例外処理はしません。その代わり、finally節で最後のログを記録 します。finally節は、例外発生の有無にかかわらず、常に実行されからです。  なお、Loggerは、デフォルトではinfo(情報)のレベルまでしか出力しません。ここでは fine(普通)レベルで出力しているので、LoggingProducer.javaで、FINE以上を出力するよ うにログレベルの変更をしています。 @Produces

public Logger getLogger() {

String className = point.getMember().getDeclaringClass(). getName();

Logger logger = Logger.getLogger(className); logger.setLevel(Level.FINE); return logger; } ※ログレベルについてはP.〇を参照してください。

メソッド用のインターセプタでの処理

 17行目のMethodLoggingがメソッド用のインターセプタメソッドです。  先頭部分で、ログ用のデータとして、クラス名(18行)、メソッド名(19行)、引数リスト(20 行)を取得しています。これらは、InvocationContextクラスの定型的な使い方でいつもこ のように書きます。  詳細はInvocationContextクラスのAPIを参照してください。 ※https://jakarta.ee/specifications/platform/9/apidocs/  メソッドの具体的な処理は次の3つです。   ①ログを記録する<22行目>   ②メソッドを実行する<25、26行目>    ・ここでのic.proceed(); は、メソッドを実行します    ・ メソッドの戻り値をresultに取得します(戻り値のないメソッドではnullが返され ます) 一行に表示する のコラム 青下線を追加 12章で作成したものと同じです。ただ、 トルツメ トルツメ ないので、 sample18-02/loggin/LoggingProducer.java 挿入 太字 太字に しない ツメル 6400 に差替え ・黒枠 ・日本語はゴシ ・1ポイント小さな  サイズにする ・黒枠 ・日本語はゴシ ・1ポイント小さな  サイズにする

(9)

13

   ・戻り値をreturn文で返します   ③ログを記録する<28行目>

2.3 インターセプタをCDIに登録する

 インターセプタは、beans.xmlに登録しないと有効になりません。JETのプロジェクトで はbeans.xmlは自動生成されているので、[Webページ]⇒[WEB-INF]の下にあります。こ れを開いて次の青枠の部分を追記します。 例題2(続き)インターセプタの登録 sample18-02/WEB-INF/beans.xml <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" ・・・省略・・・> <interceptors> <class>beans.LoggingInterceptor</class> </interceptors> </beans> classタグの中に、インターセプタのクラス名をパッケージ名を含めて指定します。

2.4 インターセプタを使う

 sample18-02プロジェクトを実行すると、番号を入力する画面が開きます。番号欄に100 ~ 103の番号を入力して送信ボタンを押すと、対応する氏名が表示されます。それ以外の 番号を入力すると「該当なし」と表示されます。  クラスはバッキングビーンと名簿クラス(Meibo.java)です。ログが見やすくなるように、 ここではMeiboクラスにだけ@Loggableを付けて、インターセプタを指定しています。 6420 に差替え

(10)

13

Chapter13 CDIの高度な機能

例題2(続き)バッキングビーン sample18-02/beans/Bb.java

1 @Named

2 @SessionScoped

3 public class Bb implements Serializable {

4 private Integer number; // 番号

5 private String name; // 氏名

6 @Inject Meibo meibo; 7

8 public String find() {

9 name = meibo.getMember(number); // 番号で指名を検索する 10 if(name==null) name = "該当なし"; 11 return null; 12 } 13 // セッター、ゲッターを省略 14 } 例題2(続き)名簿クラス sample18-02/beans/Meigo.java @Loggable // クラス全体をインターセプタ(LoggingInterceptor) の対象とする @Dependent

public class Meibo implements Serializable { private static final long serialVersionUID = 1L; private Map<Integer, String> members;

@PostConstruct private void init(){

members = Map.of(100,"田中宏",101,"佐藤一郎",102,"前田花子",104,"木村亮"); }

public String getMember(Integer number){ return members.get(number); } }  クラスに@Loggableを付けると、クラスのコンストラクタとメソッドが、実行時にイン ターセプタによってロギングされます。  名簿クラスには、デフォルトコンストラクタ、init()、getMessage()があります。@ PostConstructのついたinit()メソッドは、コンストラクタが実行された後、自動的に起動し て、Mapに名簿データを登録します。  getMember()メソッドは、番号を受け取り、Mapから該当する番号の氏名を検索して返 すメソッドです。  なお、クラスではなく、メソッドに@Loggableを付けると、そのメソッドだけをインター コメント文のスタイル(黄色) 青下線を追加 行番号 を付ける 作成 トルツメ }

(11)

13

セプトしてロギングするようにできます。 @Loggable

public String getMember(Integer number){ return members.get(number); }  さて、次が実行時のロギングの結果です。  図の左側は起動直後の状態で、Meiboクラスのコンストラクタ(デフォルトコンストラク タ)が起動したことがログからわかります。  右側は、番号に100を記入して[送信]ボタンを押した状態です。ログから、Meiboクラス のgetMemberメソッドが起動したことがわかります。起動時に引数に100を受け取り、リ ターン時に"田中宏"を戻り値として返したことも表示されています。  なお、クラス全体に@Loggableを指定していますが、init()のようなライフサイクルコー ルバックメソッドは、インターセプタの対象にならないことがわかります。

(12)

13

Chapter13 CDIの高度な機能  デコレーターは、元のクラスに変更を加えずに、メソッドの機能を変える機能です。プ ロジェクト全体に渡って、一時的に、特定のメソッドの機能を変更したいという時に便利 です。やり方は簡単で、置き換えたいメソッドだけを再定義したデコレータークラスを作 成して、beans.xmlに登録するだけです。元のクラスには、一切、手を加えません。  ただし、置き換えることができるのは、そのクラスが実装しているインタフェースのメ ソッドだけです。どんなメソッドでも機能を置き換えることができるわけではありません。 ここでは、サイコロの機能を持つDiceインタフェースを実装したDice6クラスのメソッドを、 デコレーターを使って変更してみましょう。

3.1 デコレーターを作成する

 まず、Diceインタフェースと、それを実装したDice6クラスは次のようです。 Diceインタフェース sample18-03/beans/Dice.java 例題3

public interface Dice {

public Integer playDice(); // サイコロの動作

public String info(); // 実装についての情報を返す } Section Section Section Section Section S Sectition Se Sectctioionn Section S i S ti

(13)

13

Dice6クラス sample18-03/beans/Dice6.java 例題3

@Dependent

public class Dice6 implements Dice, Serializable { private Integer n;

@Override

public Integer playDice() {

return n = new Random().nextInt(6) + 1; // 1∼6の乱数を返す }

@Override

public String info() {

return "サイコロのシミュレーション"; } // ゲッター、セッターを省略 }  DiceインタフェースにはplayDice()とinfo()という2つのメソッドがあります。playDice()は サイコロの機能をシミュレートし、info()は実装についての情報を文字列で返します。  Dice6クラスは、playDice()で1 ~ 6の乱数を返すようにしています。また、info()では「サ イコロのシミュレーション」という文字列を返します。  そこで、Dice6クラスのメソッドうち、playDice()だけを、デコレーターを使って変更し てみます。次はDice6クラスのデコレーターであるDice6_Decoratorクラスです。 例題3(続き)Dice6クラスのデコレーター sample18-03/beans/Dice6_Decorator.java 1 @Decorator

2 public abstract class Dice6_Decorator implements Dice { 3

4 @Inject @Delegate

5 Dice6 dice6; // 既存の実装クラス(Dice6)のオブジェクトをインジェクト 6

7 @Override

8 public Integer playDice() {

9 return new Random().nextInt(32) + 1; // ルーレット用に1 ∼ 32の値を 返す 10 } 11 }  青枠の部分を除くと、Diceインタフェースを実装した普通のクラスのように見えます。 しかし、クラスには@Decoretorが付いています。また、4、5行では、対象とするクラス(Dice6) のオブジェクトをインジェクトしています。しかも@Injectに、@Delegateという限定子の 例題3(続き) スタイルも 変える

(14)

13

Chapter13 CDIの高度な機能 ようなアノテーションが付いています。  実は、この部分の働きで、Dice6クラスが他のクラスにインジェクトされる動作をインター セプトしているのです。他のクラスがDice6クラスをインジェクトすると、その動作はイン ターセプトされ、インジェクトしたオブジェクトのメソッドを、デコレータークラスで定 義したメソッドで上書きします。  なお、デコレーターは変更したいメソッドだけをオーバーライドするので、抽象クラス でも構いません。例でもinfo()メソッドをオーバーライドしていないことに注意してくださ い。そのため、置き換わる機能はオーバーライドしたメソッド(playDice())だけで、後は元 のクラス(Dice6)の実装が有効です。  このように、完全にクラス全体を置き換えるのではなく、特定のメソッドだけを上書き するので、デコレート(装飾)というネーミングになっているわけです。  まとめると、デコレータークラスは次の手順で作成します。 ①クラスに@Decoratorアノテーションを付ける ②対象クラスが実装しているインタフェースのうち、  変更したいメソッドが定義されているインタフェース(Dice)を実装する ③@Injectに@Delegateを付けて、対象クラス(Dice6)のオブジェクトをインジェクトする ④インタフェースの変更したいメソッドだけをオーバーライドする(抽象クラスでもよい)  なお、例題では、インジェクトしたDice6クラスのオブジェクトを使っていませんが、使 うか使わないかは自由です。例えば、次のような実装も考えられます。 @Override

public Integer playDice() {

return dice6.playDice() + 100; // 101∼106 の間の値を返す }

3.2 beans.xmlに登録する

 デコレーターもbeans.xmlに登録すると有効になります。次のように、decoratorタグを 使って、パッケージ名を含む完全なクラス名を指定します。

(15)

13

例題3(続き)beans.xmlに登録する sample18-03/WEB-INF/beans.xml <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" ・・・省略・・・> <decorators> <class>beans.DiceDecorator</class> </decorators> </beans>

3.3 使用例

 sample18-03プロジェクトは、[サイコロを振る]ボタンを押すと、ランダムにサイコロの 値を表示します。beans.xmlにデコレーターを登録する前は、1 ~ 6の間の値を返すので、 実行すると次のように表示されます。  しかし、beans.xmlにDice6_Decoratorクラスを登録して再起動するだけでデコレータが 働くようになり、プログラムは一切変更していないにもかかわらず、次のように1 ~ 32の 間の値を返すようになります。  最後に、index.xhtmlとバッキングビーンを次に示します。 6420 に差替え

(16)

13

Chapter13 CDIの高度な機能 サイコロのシミュレーション sample18-03/index.xhtml 例題3 <h:body> <h:form> <h1>#{bb.dice6.info()}</h1>

<h:commandButton action="#{bb.next}" value="サイコロを振る" /> #{bb.n} </h:form> </h:body> sample18-03/beans/Bb.java 例題3 @Named @SessionScoped

public class Bb implements Serializable { private Integer n;

@Inject

private Dice6 dice6; // Dice6をインジェクトする public String next() {

n = dice6.playDice(); // サイコロの値を更新する return null; } // セッター、ゲッターを省略 } 例題3(続き) 例題3(続き) スタイルも 変える スタイルも 変える

(17)

13

 例えばAjaxでは、イベントの発生によりサーバーにデータを送ることができましたが、 CDIビーンでも、プログラムでイベントを発生させると、イベントの発生を感知した別の プログラムが処理を実行するようにできます。  大きな違いはイベントを起こす側と、受け取る側の依存関係がないことです。どのプロ グラムでも自由にイベントを発生でき、どのプログラムでも自由にイベントを受け取るこ とができます。

4.1 同期イベント処理

 イベントを発生させる方をイベントプロデューサーといい、受け取って処理を行う方を イベントオブザーバーといいます。  例えば、メンバー登録の処理で、①メンバーデータを入力する処理、②メンバーデータ をデータベースに登録する処理、③登録されたメンバーに登録通知メールを送信する処理 があるとします。  ①は入力が終わった時にイベントを発生させ、メンバーオブジェクトを放出します。② と③はそれぞれイベントを感知すると放出されたメンバーオブジェクトを受け取って、登 録や通知の処理を行います。次の図は、入力処理がイベントオブジェクトを放出し、DB 登録処理とメール通知処理が、オブジェクトを受け取る様子を表しています。 放出 取得 取得 DBに登録 メール通知  では、これらを具体的なプログラムで見ましょう。 Section Section Section Section Section S Sectition Se Sectctioionn Section S i S ti

4

オブザーバー: イベント駆動処理

(18)

13

Chapter13 CDIの高度な機能 イベントプロデューサー sample18-04/beans/Bb.java 例題3 1 @Named 2 @RequestScoped 3 public class Bb { 4 @Email

5 private String email; // メールアドレス

6 @NotBlank

7 private String name; // 氏名 8

9 @Inject

10 private Event<Member> event; // イベントオブジェクト 11

12 public void next() {

13 Member member = new Member(email, name);

14 event.fire(member); // イベントを発生(発火)する 15

16 email= null; name = null; // 入力画面の表示をクリアするため

17 } 18 // セッター、ゲッターを省略 19 }  JSFページで、メールアドレスと氏名を入力してコマンドボタンを押すと、バッキング ビーンのnext()メソッドが起動します。nextメソッドは、入力データからMember型のオブ ジェクトを作成し、それを引数としてイベントを発火(発生)します。イベントを発火する メソッドをイベントプロディーサーといいます

Member st = new Member(email, name);

event.fire(member); // イベントを発生(発火)する

 fireメソッドの引数(Member型オブジェクトmember)は、いわば放出されるオブジェク トで、イベントを感知したメソッドに受け取られます。

 なお、Eventオブジェクトは、9、10行目のように、CDIサービスが持っているEventオ ブジェクトをインジェクトにより取得して使います。

@Inject

private Event<Member> event; // イベントオブジェクト

 Eventオブジェクトは総称型で、イベントを通して受け渡す(放出する)オブジェクトの 4

// イベントプロデューサー

コメントを 挿入

(19)

13

型を指定します。ここではMember型です。

例題3(続き)オブザーバー 1 sample18-04/beans/Recorder.java

1 @ApplicationScoped

2 public class Recorder implements Serializable {

3 @Inject

4 private transient Logger logger;

5 List<Member> ls = new ArrayList<>(); // オブジェクトを登録するリスト 6

7 public void add(@Observes Member st) { // オブザーバーメソッド

8 ls.add(st); 9 logger.fine("登録:"+ st +" " +LocalDateTime.now()); 10 } 11 }  オブザーバーメソッドを作るのは簡単で、@Observesアノテーションをメソッドの引数 に付けるだけです。引数はイベントを通じて受け取るオブジェクトですから、ここでは、 Member型を指定します。  このRecorderクラスのaddメソッドは、オブザーバーメソッドです。バッキングビーン のnextメソッドが発火したイベントを感知すると、直ちに起動します。本来は、受け取っ たMemberオブジェクトをデータベースに登録しますが、ここでは、Listに登録する処理 にしています(データベースは〇章で解説します)。なお、登録後に、ログも記録します。 例題3(続き)オブザーバー 2 sample18-04/beans/notification.java 1 @RequestScoped

2 public class Notificator {

3 @Inject

4 EmailSender mail; 5

6 public void sendMail(@Observes Member st){

7 String to = st.getEmail(); // 宛先アドレス

8 String subject = "登録通知"; // メールタイトル

9 String body = "あなたの情報が登録されました"; // メール本文

10 mail.send(to, subject, body); // 送信処理

11 } 12 }  このNotificatorクラスのsendMailメソッドも@Obeservesアノテーションが引数に付いて 4 15   6410   を挿入 注のスタイルです

(20)

13

Chapter13 CDIの高度な機能 いるので、オブザーバーメソッドです。バッキングビーンのnextメソッドが発火したイベ ントを感知すると、直ちに起動します。  ここでは、メール送信ユーティリティのEmailSenderを使って、登録者に通知メールを 送信します。なお、EmailSenderの使い方はコラム【メール送信処理】を参照してください (GMailを使う場合はGoogleアカウントでの設定が必要です)。  なお、addメソッドとsendMailメソッドの実行は、シーケンシャルです。どちらかが終 わると、次が実行できます。どちらを先に実行すべきか指定したいときは、@Priorityアノ テーションを使って指定できます。

public void add(@Observes @Priority(1) Member st){

public void sendMail(@Observes @Priority(2) Member st){

 こうしておくと、値の小さいほうが先に実行されます。  では、sample18-04プロジェクトを実行して、結果を確認してください。サーバーログに 次のようなログが記録され、また、(コラムを読んでEmailSenderの設定が必要ですが)通 知メールも届くはずです。 サーバーログ(抜粋) sample18-04は、670ミリ秒で正常にデプロイされました。|#] 登録:tanaka@×××.com:田中宏 2020-06-25T09:26:20.853585200|#]

4.2 非同期イベント処理

 非同期イベント処理は、オブザーバーの処理が非同期に(つまり、別のスレッドでそれ ぞれコンカレントに)実行されるので、イベントを発火した後、すぐに処理が戻ってきます。 同期イベント処理では、オブザーバーの処理がすべて終わるまで待つ必要がありました。  書き方の違いは、わずかです。イベントの発火に、fireメソッドではなくfireAsyncメソッ ドを使い、受け取りの@Observesアノテーションの代わりに@ObservesAsyncを使うだけ です。  ただ、コンカレント処理なので、fireAsyncメソッドの第2引数に、スレッドプールを指 定すると効率よく実行できます※。スレッドプールとは、コンカレント処理のためのスレッ ※指定はオプションですが、本番環境では、指定することを強く推奨します。 mail.xml

(21)

13

ドをあらかじめプールしておき、効率よく使いまわすためのものです。

 ただし、Jakarta EE用のスレッドプールは、サーバーがあらかじめ作って持っているので、

@Resourcesアノテーションを使って、それを受け取る必要があります。

 ManagedExecutorServiceクラスのオブジェクトとして、次の書き方で取得します。

@Resource(lookup = "concurrent/__defaultManagedExecutorService") private ManagedExecutorService executor;

 なお、リソース名である"concurrent/__defaultManagedExecutorService"は、アプリケー ションサーバーごとに違います。上記はPayara Server(GlassFishも同じ)のものです※ 非同期イベント sample18-05/beans/Bb.java 例題4 1 @Named 2 @RequestScoped 3 public class Bb { 4 @Email

5 private String email; // メールアドレス

6 @NotBlank

7 private String name; // 氏名 8

9 @Inject

10 private Event<Member> event; // イベントオブジェクト

11 @Resource(lookup="concurrent/__defaultManagedExecutorService")

12 private ManagedExecutorService executor; // スレッドプール 13

14 public void next() {

15 Member member = new Member(email, name);

16 event.fireAsync(member, NotificationOptions.ofExecutor(executor)); 17

18 email= null; name = null; // 入力画面の表示をクリアするため

19 } 20 // セッター、ゲッターを省略 21 }  非同期イベントのBb.javaです。fireAsyncでイベントを発火します。2番目の引数はオプ ションとしてスレッドプールを指定する書き方です。   な お、Recorder.javaとNotificator.javaは@Observesが@ObservesAsyncに 変 わ っ た 太字 2つのアンダーバー

(22)

13

Chapter13 CDIの高度な機能 だけなので、掲載を省略します。プロジェクトのソースコードを見てください。では、 sample18-05を実行して、非同期処理のおかげで、すぐに制御が戻ってくることを確認し てください。 コラム メール送信処理  EmailSenderは、Jakarta EE環境で使うのに最適な電子メール送信ユーティリティ です。EmailSenderを次のように@Injectして、sendメソッドで送信するだけです。商 用利用も可能です(MITライセンス)。 @Inject EmailSender mail;

mail.send(to, from, subject, body); // to:宛先、form:送信元、subject:表題、 body:本文

 SMTP-AUTHで認証し、平文メールとHTMLメールを送信できます。

 それぞれ一斉送信と添付ファイル機能があるので、全部で8種のAPIがあります。 平文メール

 ①

public void send(to, subject, body)

 ②

public void send(to, subject, body, type)

 ③

public void send(to, subject, body, fileDir, flist)

 ④

public void send(to, subject, body, fileDir, flist, type

)

HTMLメール

 ①

public void sendHtml(to, subject, body)

 ②

public void sendHtml(to, subject, body, type)

 ③

public void sendHtml(to, subject, body, fileDir, flist)

  ④

public void sendHtml(to, subject, body, fileDir, flist,

type)

 平文、HTMLメール共に、①~④は次のような機能です。  ① 単純なメール送信  ② 1つ以上の一斉送信    "TO"なら1つだけ、"CC"、"BCC"の場合は、宛先をコンマで区切った文字列をto に指定する  ③ 1つ以上の添付ファイル   "TO" 、 CC" 、 "BCC" のどれかを指定 ファイルがあるフォル ダへの絶対パス文字列 ファイル名のリスト List<String>型 全体の フォントサイズ を1ポイント 小さくする コメントのフォントを小さくするなどして1行に収める 1行に収める ピンクの部分 グレーの網掛け で使っているのと 同じフォント、 同じフォントサイズ にします

(23)

13

  fileDirはファイルがあるフォルダへの絶対パス、flistはファイル名のリスト  ④ ②+③の機能 送信設定  メールのサーバー情報やID、パスワードは、[Webページ]直下のrosources/conf/ mail.xmlに書いておきます。次のような簡単なxmlファイルです。青字の部分を書き 換えます。 <?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties>

<entry key="smtpUser">送信者のメールアドレス</entry> <entry key="smtpPassword">送信者のメールパスワード</entry> <entry key="port">送信に使うメールサーバーのポート番号</entry> <entry key="host">送信に使うメールサーバーの完全修飾名</entry> </properties>  例えば、GoogleのGmailを使うのであれば、次のように書きます。 <properties> <entry key="smtpUser">[email protected]</entry> <entry key="smtpPassword">ikwi398Ila2</entry> <entry key="port">587</entry> <entry key="host">smtp.gmail.com</entry> </properties> ※smtpUserとsmtpPasswordは架空のものです  Mavenの<dependency>設定は例題のPOMファイルを見て下さい。また、詳細な解 説やAPIは、https://k-webs.jp/jakarta/emailsender/docs/にあります。 【Gmailを利用する際の注意】  Gmailを使う場合は、Googleアカウントのセキュリティページにアクセスして、[安 全性の低いアプリのアクセス]を[オン]に設定する必要があります。 ⇒ https://myaccount.google.com/security 全体の フォントサイズ を1ポイント 小さくする

(24)

13

Chapter13 CDIの高度な機能

要点

※理解した項目にはチェックを入れましょう

ライフサイクル・コールバックメソッド

□ オブジェクトのライフサイクルに連動して動作するメソッドを、ライフサイクル・コールバック メソッドという □ライフサイクル・コールバックメソッドは次のように作成する  (1)メソッドは、戻り値と引数を持たない  (2)コンストラクタの直後に起動するメソッドには@PostConstructを付けて定義する  (3)オブジェクトの廃棄直前に起動するメソッドには@PreDestroyを付けて定義する

インターセプタ

□コンストラクタやメソッドの実行前後に割り込んで、ロギングなどの指定した処理を実行する □インターセプタ・バインディングのために、カスタムアノテーションを作成してから、インターセ プタを作成する □インターセプタの作成は次のようにする  (1)定義するクラスには、@Intercepterと作成したカスタムアノテーションを付ける  (2)コンストラクタに割り込む処理には、@ AroundConstructを付ける  (3)メソッドに割り込む処理には、@AroundInvokeを付ける  (4)書き方のスタイルが決まっている(例題を見る)  (5)beans.xmlに<intercepter>タグで登録すると使えるようになる □インターセプタを使うには、適用したいクラスやメソッドにカスタムアノテーションを付けるだけ でよい

デコレーター

□デコレーターは既存のクラスのメソッドの機能を後から変更するために使う □変更できるのは、既存のクラスが実装しているインタフェースのメソッドに限られる □デコレーターの作成は次のようにする  (1)定義するクラスには、@Decoratorを付ける  (2)対象のクラスが実装しているインタフェースで、変更したいメソッドを含むものを実装する  (3)@Inject、@Delegateを付けて、対象クラスのオブジェクトをインジェクトする Section Section Section Section Section S Sectition Se Sectctioionn Section S i S ti

(25)

13

 (4)変更したいメソッドだけをオーバーライドする  (5)beans.xmlに<decorator>タグで登録すると使えるようになる □オブザーバーは、イベントプロデューサーが発生させたイベントを受け取って起動する(イベント 駆動) □イベントを発生する方をイベントプロデューサーといい、受け取って起動する方をオブザーバー という □1つのイベントプロデューサーに対して、複数のオブザーバーが存在してよい □同期イベントでは、複数のオブザーバーがシーケンシャルに起動するが、非同期イベントでは異 なるスレッドを使って、コンカレントに起動するので効率よく実行できる □同期イベントでは、fireメソッドと@Observesアノテーションを使う □非同期イベントでは、fireAsyncメソッドと@ObservesAsyncアノテーションを使う □非同期イベントはは、サーバーリソースからスレッドプールを取得して、fireAsyncの第2引数に 指定すると、コンカレント処理が確実に効率よく実行される

練習

1.sample18-05プロジェクトと同じものが、exerciseProjectフォルダに、exercise_chap13-1 として入っています。このプロジェクトのBb.java、Notificator.java、Recorder.javaクラス に、sample18-02プロジェクトで作成したロギングインターセプタを適用して実行しなさい。  具体的には、次のクラスをexercise_chap13-1/util/フォルダにコピーするといいでしょう。   sample18-02/beans/Loggable.java   sample18-02/beans/LoggingInteceptor.java  また、exercise18-1/WEB-INF/beans.xmlに、インターセプタを登録する必要があります。 2.exercise_chap13-2プロジェクトを開いて、イベントプロデューサーとオブザーバーの処 理を作成してください。処理内容は次の通りです。なお、index.xhtmlは完成形のものが最 初から入っています。必要なクラスも作ってありますが、内容は外形だけのスケルトンです。 必要なインジェクションやメソッド引数、処理などを考えて、完成してください。  ①ウェブで数値を入力してコマンドボタンを押すと、Bb.javaのnext()メソッドが呼び出 されます。  ②next()メソッドはイベントプロデューサーで、呼び出されるとfireAsyncメソッドで発 火し、フィールド変数のnumberを放出します  ③ObserverAクラスには、オブザーバーメソッドとしてlog()メソッドがあります。イベ ントを感知すると、numberを受け取り、値を2倍して「2倍の値=×××」というようにログ に記録します。 トルツメ トルツメ

(26)

13

Chapter13 CDIの高度な機能  ④ObserverBクラスにも、オブザーバーメソッドとしてlog()メソッドがあります。イベ ントを感知すると、numberを受け取り、値を4倍して「4倍の値=×××」というようにログ に記録します。   な お、 ロ グ を 取 る た め に、exercise_chap13-2プ ロ ジ ェ クト のutilパ ッ ケ ー ジ に、 LoggerProducer.javaが入っています。

参照

関連したドキュメント

このたび、第4回令和の年金広報コンテストを開催させていただきま

スライド5頁では

詳細はこちら

Jabra Talk 15 SE の操作は簡単です。ボタンを押す時間の長さ により、ヘッドセットの [ 応答 / 終了 ] ボタンはさまざまな機

これはつまり十進法ではなく、一進法を用いて自然数を表記するということである。とは いえ数が大きくなると見にくくなるので、.. 0, 1,

Windows Hell は、指紋または顔認証を使って Windows 10 デバイスにアクセスできる、よ

このエアコンは冷房運転時のドレン(除湿)水を内部で蒸発さ

操作は前章と同じです。但し中継子機の ACSH は、親機では無く中継器が送信する電波を受信します。本機を 前章①の操作で