13
第 章
CDIの高度な機能
この章では、CDIビーンが持つ便利な機能の数々を一挙に紹介します。開発実務で は欠かせない機能です。中でも、メソッドの開始前後に割り込んでログを取ったりで きるインターセプタは特に有用です。その他、コンストラクタの直後に実行する処理 を定義できるライフサイクルコールバックメソッド、既存のメソッドの機能を外部か ら変更できるデコレーター、イベントを発生させて、それに対応する処理を作成し、
コンカレントに実行できるオブザーバーなど、楽しい機能が満載の章です。
1 コールバックメソッド ...
2 インターセプタ(割り込み処理) ...
3 デコレーター ...
4 オブザーバー(イベント駆動処理) ...
5 まとめ ...
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コールバックメソッド
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
メソッドが実行され③が表示されます。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
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インターセプター(割り込み処理)
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 { ・・・
}
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; // 12章4.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つです
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() で索引語にする
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() で索引語にする
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
を付けると、そのメソッドだけをインター13
Chapter13 CDIの高度な機能
セプトしてロギングするようにできます。
@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 例題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
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; // ルーレット用に1〜32の値を返す 10 }
11 }
青枠の部分を除くと、
Dice
インタフェースを実装した普通のクラスのように見えます。しかし、クラスには
@Decoretor
が付いています。また、4、5行では、対象とするクラス(
Dice6
)のオブジェクトをインジェクトしています。しかも@Inject
に、@Delegate
と いう限定子のようなアノテーションが付いています。13
Chapter13 CDIの高度な機能
実は、この部分の働きで、
Dice6
クラスが他のクラスにインジェクトされる動作をイン ターセプトしているのです。他のクラスがDice6
クラスをインジェクトすると、その動作 はインターセプトされ、インジェクトしたオブジェクトのメソッドを、デコレータークラス で定義したメソッドで上書きします。なお、デコレーターは変更したいメソッドだけをオーバーライドするので、抽象クラス でも構いません。例でも
info()
メソッドをオーバーライドしていないことに注意してくだ さい。そのため、置き換わる機能はオーバーライドしたメソッド(playDice()
)だけで、後は元のクラス(
Dice6
)の実装が有効です。このように、完全にクラス全体を置き換えるのではなく、特定のメソッドだけを上書き するので、デコレート(装飾)というネーミングになっているわけです。
まとめると、デコレータークラスは次の手順で作成します。
①クラスに@Decoratorアノテーションを付ける
②対象クラスが実装しているインタフェースのうち、
変更したいメソッドが定義されているインタフェース(Dice)を実装する
③@Injectに@Delegateを付けて、対象クラス(Dice6)のオブジェクトをインジェクトする
④インタフェースの変更したいメソッドだけをオーバーライドする(抽象クラスでもよい)
なお、
Dice6_Decorator
クラスでは、インジェクトしたDice6
クラスのオブジェクトを 使っていませんが、使うか使わないかは自由です。例えば、次のような実装も考えられます。@Override
public Integer playDice() {
return dice6.playDice() + 100; // 101〜106 の間の値を返す }
3.2 beans.xmlに登録する
デコレーターも
beans.xml
に登録すると有効になります。次のように、decorator
タ グを使って、パッケージ名を含む完全なクラス名を指定します。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>
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;
}
// セッター、ゲッターを省略
}
13
Chapter13 CDIの高度な機能
例えばAjaxでは、イベントの発生によりサーバーにデータを送ることができましたが、
CDIビーンでも、プログラムでイベントを発生させると、イベントの発生を感知した別の プログラムが処理を実行するようにできます。
大きな違いはイベントを起こす側と、受け取る側の依存関係がないことです。どのプロ グラムでも自由にイベントを発生でき、どのプログラムでも自由にイベントを受け取るこ とができます。
4.1 同期イベント処理
イベントを発生させる方をイベントプロデューサーといい、受け取って処理を行う方を イベントオブザーバーといいます。
例えば、メンバー登録の処理で、①メンバーデータを入力する処理、②メンバーデータ をデータベースに登録する処理、③登録されたメンバーに登録通知メールを送信する処理 があるとします。
①は入力が終わった時にイベントを発生させ、メンバーオブジェクトを放出します。② と③はそれぞれイベントを感知すると放出されたメンバーオブジェクトを受け取って、登 録や通知の処理を行います。次の図は、入力処理がイベントオブジェクトを放出し、DB 登録処理とメール通知処理が、オブジェクトを受け取る様子を表しています。
放出
取得
取得
DBに登録
メール通知
では、これらを具体的なプログラムで見ましょう。
Section Section Section Section Section S Sectition Se Sectctioionn Section
S i
4
S tiオブザーバー(イベント駆動処理)
オブザーバー イベント駆動処理
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
オブジェクトは総称型で、イベントを通して受け渡す(放出する)オブジェクトの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ファイルのコメントを参 照してください
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引数に、スレッドプールを優先順の指定 このぺージで
索引語とする
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指定はオプションですが、本番環境では、指定することを強く推奨します。
フォントを
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>型
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
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まとめ
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倍の値=×××」というようにログに記録しま す。
13
Chapter13 CDIの高度な機能
なお、ログを取るために、exercise_chap13-2プロジェクトのutilパッケージに、Logger Producer.javaが入っています。