13
第
章
CDIの高度な機能
この章では、CDIビーンが持つ便利な機能の数々を一挙に紹介します。開発実務で は欠かせない機能です。中でも、メソッドの開始前後に割り込んでログを取ったりで きるインターセプタは特に有用です。その他、コンストラクタの直後に実行する処理 を定義できるライフサイクルコールバックメソッド、既存のメソッドの機能を外部か ら変更できるデコレーター、イベントを発生させて、それに対応する処理を作成し、 コンカレントに実行できるオブザーバーなど、楽しい機能が満載の章です。 1.コールバックメソッド: 初期化と終了処理 2.インターセプタ: 割り込み処理 3.デコレーター: メソッドの機能を変更する 4.オブザーバー: イベント駆動処理 5.まとめ 1 コールバックメソッド ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 2 インターセプタ(割り込み処理) ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 3 デコレーター ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 4 オブザーバー(イベント駆動処理) ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 5 まとめ ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 他の章と同じスタイルにしてください (指示の間違いでした) ◎ほかの章も含めて全体にかかること 「まとめ」と「練習問題」は本文よりも1ポイント小さなフォントを使ってください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 ti1
コールバックメソッド: 初期化と終了処理
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
メソッドが実行され③が表示されます。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.の後で変わりません。13
インターセプタは、メソッドやコンストラクタの処理の前後に割り込んで、何かの処理 を実行するプログラムです。例えば、クラスのすべてのメソッドに対して、実行の直前に 割り込んでログを取る、などが代表的な機能です。 インターセプタは、1つだけ作成しておくと、あらゆるメソッド・コンストラクタにインジェ クトできます。ただし、変数ではなく、クラスやメソッド、コンストラクタに対して、インジェ クトするので、@Injectは使えません。代わりに"カスタムアノテーション"を使います。 カスタムアノテーションをクラスやメソッドに書いておくと、それだけで、特定のイン ターセプタがインジェクトされます。このように、カスタムアノテーションを使って特定 のインターセプタをCDIに紐づけることを、インターセプタ・バインディングといいます。 そこで、インターセプタを利用するには、インターセプタ本体と共に、カスタムアノテー ションを作る必要があります。2.1 カスタムアノテーションの作成
インターセプタの種類ごとに、カスタムアノテーションが必要です。まず、カスタムア ノテーションから作っておきましょう。 ここでは、割り込み先でログを取るインターセプタを作るので、カスタムアノテーショ ンは、@Loggableにします。NetBeansでは、名前を指定するだけで作成できます。 【作成手順】 ①プロジェクトを右ボタンでクリックし、[新規]⇒[その他]と選択する ⇒新規ファイルダイアログが開く ② カテゴリ欄で[コンテキストと依存性の注入]、ファイルタイプ欄で[インターセプタ結合型] を選んで[次>]ボタンを押す ⇒ ダイアログが開く ③ダイアログの[クラス名]にLoggableと入力して[終了]を押す 以上で、次のようなカスタムアノテーションの定義が生成されます。 Section Section Section Section Section S Sectition Se Sectctioionn Section S i S ti2
インターセプター: 割り込み処理
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 @Loggablepublic 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 { ・・・
}
13
コンストラクタ用には、@AroundConstruct、メソッド用には、@AroundInvokeを付け ます。どちらも、実行時の情報を持つInvocationContextを引数にとります。また、例外処 理はしないので、throws Exceptionを付けて宣言します。 次の例題で詳しく見てみましょう。 例題2(続き)インターセプタ sample18-02/beans/LoggingInterceptor.java 1 @Interceptor 2 @Loggable3 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節で作成したロガー コメントを追加13
Chapter13 CDIの高度な機能 ①ログを記録する<9行目> ・ic.getConstructor().getName()は、"コンストラクタ名"を取得する書き方です ②コンストラクタを実行する<11行目> ・ic.proceed(); は、コンストラクタを実行する命令です ③ログを記録する<13行目> try文になっているのは、ic.proceed();でコンストラクタを実行すると例外を発生する可 能性があるからですが、例外処理はしません。その代わり、finally節で最後のログを記録 します。finally節は、例外発生の有無にかかわらず、常に実行されからです。 なお、Loggerは、デフォルトではinfo(情報)のレベルまでしか出力しません。ここでは fine(普通)レベルで出力しているので、LoggingProducer.javaで、FINE以上を出力するよ うにログレベルの変更をしています。 @Producespublic 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ポイント小さな サイズにする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 に差替え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を付けると、そのメソッドだけをインター コメント文のスタイル(黄色) 青下線を追加 行番号 を付ける 作成 トルツメ }
13
セプトしてロギングするようにできます。 @Loggable
public String getMember(Integer number){ return members.get(number); } さて、次が実行時のロギングの結果です。 図の左側は起動直後の状態で、Meiboクラスのコンストラクタ(デフォルトコンストラク タ)が起動したことがログからわかります。 右側は、番号に100を記入して[送信]ボタンを押した状態です。ログから、Meiboクラス のgetMemberメソッドが起動したことがわかります。起動時に引数に100を受け取り、リ ターン時に"田中宏"を戻り値として返したことも表示されています。 なお、クラス全体に@Loggableを指定していますが、init()のようなライフサイクルコー ルバックメソッドは、インターセプタの対象にならないことがわかります。
13
Chapter13 CDIの高度な機能 デコレーターは、元のクラスに変更を加えずに、メソッドの機能を変える機能です。プ ロジェクト全体に渡って、一時的に、特定のメソッドの機能を変更したいという時に便利 です。やり方は簡単で、置き換えたいメソッドだけを再定義したデコレータークラスを作 成して、beans.xmlに登録するだけです。元のクラスには、一切、手を加えません。 ただし、置き換えることができるのは、そのクラスが実装しているインタフェースのメ ソッドだけです。どんなメソッドでも機能を置き換えることができるわけではありません。 ここでは、サイコロの機能を持つDiceインタフェースを実装したDice6クラスのメソッドを、 デコレーターを使って変更してみましょう。3.1 デコレーターを作成する
まず、Diceインタフェースと、それを実装したDice6クラスは次のようです。 Diceインタフェース sample18-03/beans/Dice.java 例題3public interface Dice {
public Integer playDice(); // サイコロの動作
public String info(); // 実装についての情報を返す } Section Section Section Section Section S Sectition Se Sectctioionn Section S i S ti
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(続き) スタイルも 変える
13
Chapter13 CDIの高度な機能 ようなアノテーションが付いています。 実は、この部分の働きで、Dice6クラスが他のクラスにインジェクトされる動作をインター セプトしているのです。他のクラスがDice6クラスをインジェクトすると、その動作はイン ターセプトされ、インジェクトしたオブジェクトのメソッドを、デコレータークラスで定 義したメソッドで上書きします。 なお、デコレーターは変更したいメソッドだけをオーバーライドするので、抽象クラス でも構いません。例でもinfo()メソッドをオーバーライドしていないことに注意してくださ い。そのため、置き換わる機能はオーバーライドしたメソッド(playDice())だけで、後は元 のクラス(Dice6)の実装が有効です。 このように、完全にクラス全体を置き換えるのではなく、特定のメソッドだけを上書き するので、デコレート(装飾)というネーミングになっているわけです。 まとめると、デコレータークラスは次の手順で作成します。 ①クラスに@Decoratorアノテーションを付ける ②対象クラスが実装しているインタフェースのうち、 変更したいメソッドが定義されているインタフェース(Dice)を実装する ③@Injectに@Delegateを付けて、対象クラス(Dice6)のオブジェクトをインジェクトする ④インタフェースの変更したいメソッドだけをオーバーライドする(抽象クラスでもよい) なお、例題では、インジェクトしたDice6クラスのオブジェクトを使っていませんが、使 うか使わないかは自由です。例えば、次のような実装も考えられます。 @Overridepublic Integer playDice() {
return dice6.playDice() + 100; // 101∼106 の間の値を返す }
3.2 beans.xmlに登録する
デコレーターもbeans.xmlに登録すると有効になります。次のように、decoratorタグを 使って、パッケージ名を含む完全なクラス名を指定します。
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 に差替え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(続き) スタイルも 変える スタイルも 変える
13
例えばAjaxでは、イベントの発生によりサーバーにデータを送ることができましたが、 CDIビーンでも、プログラムでイベントを発生させると、イベントの発生を感知した別の プログラムが処理を実行するようにできます。 大きな違いはイベントを起こす側と、受け取る側の依存関係がないことです。どのプロ グラムでも自由にイベントを発生でき、どのプログラムでも自由にイベントを受け取るこ とができます。4.1 同期イベント処理
イベントを発生させる方をイベントプロデューサーといい、受け取って処理を行う方を イベントオブザーバーといいます。 例えば、メンバー登録の処理で、①メンバーデータを入力する処理、②メンバーデータ をデータベースに登録する処理、③登録されたメンバーに登録通知メールを送信する処理 があるとします。 ①は入力が終わった時にイベントを発生させ、メンバーオブジェクトを放出します。② と③はそれぞれイベントを感知すると放出されたメンバーオブジェクトを受け取って、登 録や通知の処理を行います。次の図は、入力処理がイベントオブジェクトを放出し、DB 登録処理とメール通知処理が、オブジェクトを受け取る様子を表しています。 放出 取得 取得 DBに登録 メール通知 では、これらを具体的なプログラムで見ましょう。 Section Section Section Section Section S Sectition Se Sectctioionn Section S i S ti4
オブザーバー: イベント駆動処理
13
Chapter13 CDIの高度な機能 イベントプロデューサー sample18-04/beans/Bb.java 例題3 1 @Named 2 @RequestScoped 3 public class Bb { 4 @Email5 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
// イベントプロデューサー
コメントを 挿入
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 を挿入 注のスタイルです
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.xml13
ドをあらかじめプールしておき、効率よく使いまわすためのものです。
ただし、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つのアンダーバー
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行に収める ピンクの部分 グレーの網掛け で使っているのと 同じフォント、 同じフォントサイズ にします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ポイント 小さくする