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

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

N/A
N/A
Protected

Academic year: 2022

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

Copied!
26
0
0

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

全文

(1)

13

CDIの高度な機能

 この章では、CDIビーンが持つ便利な機能の数々を一挙に紹介します。開発実務で は欠かせない機能です。中でも、メソッドの開始前後に割り込んでログを取ったりで きるインターセプタは特に有用です。その他、コンストラクタの直後に実行する処理 を定義できるライフサイクルコールバックメソッド、既存のメソッドの機能を外部か ら変更できるデコレーター、イベントを発生させて、それに対応する処理を作成し、

コンカレントに実行できるオブザーバーなど、楽しい機能が満載の章です。

1 コールバックメソッド ...

2 インターセプタ(割り込み処理) ...

3 デコレーター ...

4 オブザーバー(イベント駆動処理) ...

5 まとめ ...

(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

1

S ti

コールバックメソッド

(3)

13

Chapter13 CDIの高度な機能

コールバックメソッド 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

Chapter13 CDIの高度な機能

 インターセプタは、メソッドやコンストラクタの処理の前後に割り込んで、何かの処理 を実行するプログラムです。例えば、クラスのすべてのメソッドに対して、実行の直前に 割り込んでログを取る、などが代表的な機能です。

 インターセプタは、1つだけ作成しておくと、あらゆるメソッド・コンストラクタにインジェ クトできます。ただし、変数ではなく、クラスやメソッド、コンストラクタに対して、インジェ クトするので、@

Inject

は使えません。代わりに"カスタムアノテーション"を使います。

 カスタムアノテーションをクラスやメソッドに書いておくと、それだけで、特定のイン ターセプタがインジェクトされます。このように、カスタムアノテーションを使って特定 のインターセプタをCDIに紐づけることを、インターセプタ・バインディングといいます。

 そこで、インターセプタを利用するには、インターセプタ本体と共に、カスタムアノテー ションを作る必要があります。

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

 インターセプタの種類ごとに、カスタムアノテーションが必要です。まず、カスタムア ノテーションから作っておきましょう。

 ここでは、割り込み先でログを取るインターセプタを作るので、カスタムアノテーショ ンは、

@Loggable

にします。NetBeansでは、名前を指定するだけで作成できます。

【作成手順】

①プロジェクトを右ボタンでクリックし、[新規]⇒[その他]と選択する  ⇒新規ファイルダイアログが開く

② カテゴリ欄で[コンテキストと依存性の注入]、ファイルタイプ欄で[インターセプタ結合型]

を選んで[次>]ボタンを押す ⇒ ダイアログが開く

③ダイアログの[クラス名]にLoggableと入力して[終了]を押す

 以上で、次のようなカスタムアノテーションの定義が生成されます。

Section Section Section Section Section S Sectition Se Sectctioionn Section

S i

2

S ti

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

(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 LoggingInterceptor 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

Chapter13 CDIの高度な機能

 コンストラクタ用には、

@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; // 124.4節で作成したロガー

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つです

(8)

13

Chapter13 CDIの高度な機能

①ログを記録する<9行目>

 ・ic.getConstructor().getName()は、"コンストラクタ名"を取得する書き方です

②コンストラクタを実行する<11行目>

 ・ic.proceed(); は、コンストラクタを実行する命令です

③ログを記録する<13行目>

 

try

文になっているのは、

ic.proceed();

でコンストラクタを実行すると例外を発生 する可能性があるからですが、例外処理はしません。その代わり、

finally

節で最後のロ グを記録します。

finally

節は、例外発生の有無にかかわらず、常に実行されからです。

 なお、

Logger

は、12章で作成したものと同じです。ただ、デフォルトでは

info

(情報)

のレベルまでしか出力しないので、

FINE

以上を出力するようにログレベルの変更をしてい ます。

sample18-02/loggin/LoggingProducer.java @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

クラスの定型的な使い方でいつ もこのように書きます。

 詳細はAPIドキュメントで、javax.interceptor.InvocationContextを参照してください。

 ※https://jakarta.ee/specifications/platform/9/apidocs/

InvocationContext#proceed() で索引語にする

(9)

13

Chapter13 CDIの高度な機能

 メソッドの具体的な処理は次の3つです。

①ログを記録する<22行目>

②メソッドを実行する<25、26行目>

 ・ここでのic.proceed(); は、メソッドを実行します

 ・ メソッドの戻り値をresultに取得します(戻り値のないメソッドではnullが返されます)

 ・戻り値をreturn文で返します

③ログを記録する<28行目>

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

 インターセプタは、

beans.xml

に登録しないと有効になりません。JETのプロジェクト では

beans.xml

は自動生成されているので、[Webページ]⇒[WEB-INF]の下にあります。

これを開いて次の青枠の部分を追記します。

例題2(続き)インターセプタの登録 sample18-02/WEB-INF/beans.xml

<beans xmlns="https://jakarta.ee/xml/ns/jakartaee" ・・・ 以下省略 ・・・>

<interceptors>

<class>beans.LoggingInterceptor</class>

</interceptors>

</beans>

class

タグの中に、インターセプタのクラス名をパッケージ名を含めて指定します。

2.4 インターセプタを使う

 sample18-02プロジェクトを実行すると、番号を入力する画面が開きます。番号欄に100

~ 103の番号を入力して送信ボタンを押すと、対応する氏名が表示されます。それ以外の 番号を入力すると「該当なし」と表示されます。

 クラスはバッキングビーンと名簿クラス(

Meibo.java

)です。ログが見やすくなるよう に、ここでは

Meibo

クラスにだけ

@Loggable

を付けて、インターセプタを指定しています。

InvocationContext#proceed() で索引語にする

(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 1 @Loggable // クラス全体をインターセプタ(LoggingInterceptor) の対象とする

2 @Dependent

3 public class Meibo implements Serializable {

4 private static final long serialVersionUID = 1L;

5 private Map<Integer, String> members;

6

7 @PostConstruct

8 private void init(){

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

10 }

11 public String getMember(Integer number){

12 return members.get(number);

13 }

14 }

 クラスに

@Loggable

を付けると、クラスのコンストラクタとメソッドが、実行時にイン ターセプタによってロギングされます。

 名簿クラスには、デフォルトコンストラクタ、

init()

getMessage()

があります。

@PostConstruct

のついた

init()

メソッドは、コンストラクタが実行された後、自動的 に起動して、名簿データを作成します。

 

getMember()

メソッドは、番号を受け取り、

Map

から該当する番号の氏名を検索して 返すメソッドです。

 なお、クラスではなく、メソッドに

@Loggable

を付けると、そのメソッドだけをインター

(11)

13

Chapter13 CDIの高度な機能

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

@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

3

S ti

デコレーター

(13)

13

Chapter13 CDIの高度な機能

例題3(続き)Dice6クラス sample18-03/beans/Dice6.java

@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; // ルーレット用に132の値を返す 10 }

11 }

 青枠の部分を除くと、

Dice

インタフェースを実装した普通のクラスのように見えます。

しかし、クラスには

@Decoretor

が付いています。また、4、5行では、対象とするクラス

Dice6

)のオブジェクトをインジェクトしています。しかも

@Inject

に、

@Delegate

と いう限定子のようなアノテーションが付いています。

(14)

13

Chapter13 CDIの高度な機能

 実は、この部分の働きで、

Dice6

クラスが他のクラスにインジェクトされる動作をイン ターセプトしているのです。他のクラスが

Dice6

クラスをインジェクトすると、その動作 はインターセプトされ、インジェクトしたオブジェクトのメソッドを、デコレータークラス で定義したメソッドで上書きします。

 なお、デコレーターは変更したいメソッドだけをオーバーライドするので、抽象クラス でも構いません。例でも

info()

メソッドをオーバーライドしていないことに注意してくだ さい。そのため、置き換わる機能はオーバーライドしたメソッド(

playDice()

)だけで、

後は元のクラス(

Dice6

)の実装が有効です。

 このように、完全にクラス全体を置き換えるのではなく、特定のメソッドだけを上書き するので、デコレート(装飾)というネーミングになっているわけです。

 まとめると、デコレータークラスは次の手順で作成します。

①クラスに@Decoratorアノテーションを付ける

②対象クラスが実装しているインタフェースのうち、

 変更したいメソッドが定義されているインタフェース(Dice)を実装する

③@Injectに@Delegateを付けて、対象クラス(Dice6)のオブジェクトをインジェクトする

④インタフェースの変更したいメソッドだけをオーバーライドする(抽象クラスでもよい)

 なお、

Dice6_Decorator

クラスでは、インジェクトした

Dice6

クラスのオブジェクトを 使っていませんが、使うか使わないかは自由です。例えば、次のような実装も考えられます。

@Override

public Integer playDice() {

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

3.2 beans.xmlに登録する

 デコレーターも

beans.xml

に登録すると有効になります。次のように、

decorator

タ グを使って、パッケージ名を含む完全なクラス名を指定します。

(15)

13

Chapter13 CDIの高度な機能

例題3(続き)beans.xmlに登録する sample18-03/WEB-INF/beans.xml

<beans xmlns="https://jakarta.ee/xml/ns/jakartaee" ・・・ 以下省略 ・・・>

<decorators>

<class>beans.DiceDecorator</class>

</decorators>

</beans>

3.3 使用例

 sample18-03プロジェクトは、[サイコロを振る]ボタンを押すと、ランダムにサイコロの 値を表示します。

beans.xml

にデコレーターを登録する前は、1 ~ 6の間の値を返すので、

実行すると次のように表示されます。

 しかし、

beans.xml

Dice6_Decorator

クラスを登録して再起動するだけでデコレー タが働くようになり、プログラムは一切変更していないにもかかわらず、次のように1 ~ 32の間の値を返すようになります。

 最後に、

index.xhtm

lとバッキングビーンを次に示します。

例題3(続き)サイコロのシミュレーション sample18-03/index.xhtml <h:body>

<h:form>

<h1>#{bb.dice6.info()}</h1>

<h:commandButton action="#{bb.next}" value="サイコロを振る" />

#{bb.n}

</h:form>

</h:body>

(16)

13

Chapter13 CDIの高度な機能

例題3(続き) sample18-03/beans/Bb.java

@Named

@SessionScoped

public class Bb implements Serializable { private Integer n;

@Inject

private Dice6 dice6; // Dice6をインジェクトする

public String next() {

n = dice6.playDice(); // サイコロの値を更新する

return null;

}

// セッター、ゲッターを省略

}

(17)

13

Chapter13 CDIの高度な機能

 例えばAjaxでは、イベントの発生によりサーバーにデータを送ることができましたが、

CDIビーンでも、プログラムでイベントを発生させると、イベントの発生を感知した別の プログラムが処理を実行するようにできます。

 大きな違いはイベントを起こす側と、受け取る側の依存関係がないことです。どのプロ グラムでも自由にイベントを発生でき、どのプログラムでも自由にイベントを受け取るこ とができます。

4.1 同期イベント処理

 イベントを発生させる方をイベントプロデューサーといい、受け取って処理を行う方を イベントオブザーバーといいます。

 例えば、メンバー登録の処理で、①メンバーデータを入力する処理、②メンバーデータ をデータベースに登録する処理、③登録されたメンバーに登録通知メールを送信する処理 があるとします。

 ①は入力が終わった時にイベントを発生させ、メンバーオブジェクトを放出します。② と③はそれぞれイベントを感知すると放出されたメンバーオブジェクトを受け取って、登 録や通知の処理を行います。次の図は、入力処理がイベントオブジェクトを放出し、DB 登録処理とメール通知処理が、オブジェクトを受け取る様子を表しています。

放出

取得

取得

DBに登録

メール通知

 では、これらを具体的なプログラムで見ましょう。

Section Section Section Section Section S Sectition Se Sectctioionn Section

S i

4

S ti

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

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

(18)

13

Chapter13 CDIの高度な機能

イベントプロデューサー sample18-04/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 // イベントプロデューサー

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

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

(19)

13

Chapter13 CDIの高度な機能

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

Member

型です。

例題4(続き)オブザーバー 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

に登録す る処理にしています(データベースは15章で解説します)。なお、登録後に、ログも記録し ます。

例題4(続き)オブザーバー 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 }

※送信するには、/resources/conf/mail.xmlにパラメータを記入する必要があります。なお、Gmailを使う 場合はGmailのセキュリティ設定を変更する必要があります。詳細は、mail.xlmファイルのコメントを参 照してください

(20)

13

Chapter13 CDIの高度な機能

 この

Notificator

クラスの

sendMail()

メソッドも

@Obeserves

アノテーションが引 数に付いているので、オブザーバーメソッドです。バッキングビーンの

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プロジェクトを実行して、結果を確認してください。サーバーログに 次のようなログが記録され、また、(

mail.xml

の設定が必要ですが)通知メールも届くは ずです。

サーバーログ(抜粋)

sample18-04は、670ミリ秒で正常にデプロイされました。|#]

登録:tanaka@×××.com:田中宏 2020-06-25T09:26:20.853585200|#]

4.2 非同期イベント処理

 非同期イベント処理は、オブザーバーの処理が非同期に(つまり、別のスレッドでそれ ぞれコンカレントに)実行されるので、イベントを発火した後、すぐに処理が戻ってきます。

同期イベント処理では、オブザーバーの処理がすべて終わるまで待つ必要がありました。

  書 き 方 の 違 い は、わ ず か で す。 イベ ントの 発 火 に、

fire()

メソッド で は なく

fireAsync()

メソッドを使い、受け取りの

@Observes

アノテーションの代わりに

@ObservesAsync

を使うだけです。

 ただ、コンカレント処理なので、

fireAsync()

ソッドの第2引数に、スレッドプールを

優先順の指定 このぺージで

索引語とする

(21)

13

Chapter13 CDIの高度な機能

指定すると効率よく実行できます※1。スレッドプールとは、コンカレント処理のためのス レッドをあらかじめプールしておき、効率よく使いまわすためのものです。

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

@Resources

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

 

ManagedExecutorService

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

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

 なお、リソース名である"concurrent/__defaultManagedExecutorService"は、アプリケー ションサーバーごとに違います。上記はPayara Server(GlassFishも同じ)のものです※2

非同期イベント 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

※1指定はオプションですが、本番環境では、指定することを強く推奨します。

フォントを

(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つ以上の添付ファイル

   fileDirはファイルがあるフォルダへの絶対パス、flistはファイル名のリスト  ④ ②+③の機能

"TO" CC" "BCC" のどれかを指定

ファイルがあるフォルダ への絶対パス文字列

ファイル名のリスト List<String>

(23)

13

Chapter13 CDIの高度な機能

送信設定

 メールのサーバー情報や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

(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

5

S ti

まとめ

(25)

13

Chapter13 CDIの高度な機能

 (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倍の値=×××」というようにログに記録します。

 ④ObserverBクラスにも、オブザーバーメソッドとしてlog()メソッドがあります。イベント を感知すると、numberを受け取り、値を4倍して「4倍の値=×××」というようにログに記録しま す。

(26)

13

Chapter13 CDIの高度な機能

 なお、ログを取るために、exercise_chap13-2プロジェクトのutilパッケージに、Logger Producer.javaが入っています。

参照

関連したドキュメント

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

スライド5頁では

詳細はこちら

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

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

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

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

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