The Java EE 7 Tutorialの54 Using Java EE Interceptorsの章をテキトーに訳した。
54 Using Java EE Interceptors
この章では、メソッドやターゲットクラスのライフサイクルイベントに割り込むインターセプタークラスとメソッドの作成方法について解説します。
以下のトピックを扱います。
- Overview of Interceptors
- Using Interceptors
- The interceptor Example Application
54.1 Overview of Interceptors
インターセプターはJava EEのマネージドクラスと組み合わせて使用するもので、開発者はメソッド実行やライフサイクルイベントに連動して、関連ターゲットクラス(target class)のインターセプターメソッドを実行できます。インターセプターのよくある使い方は、ログ・監査・プロファイリング、です。
インターセプターはEnterprise JavaBeans 3.2と Contexts and Dependency Injection for Java EE 1.1の一部ですが、Interceptors 1.2仕様はJSR 318のmaintenance releaseの一部としてダウンロードが可能で、Enterprise JavaBeans 3.1は https://jcp.org/en/jsr/detail?id=318 で参照可能です。インターセプターは、セッションビーン・メッセージドリブンビーン・CDIのマネージドビーンと共に使用可能です。この場合には、インターセプターのターゲットクラスはビーンクラスになります。
インターセプターはインターセプターメソッド(interceptor method)としてターゲットクラス内部に定義したり、インターセプタークラス(interceptor class)と呼ばれる関連クラスとして定義が可能です。インターセプタークラスにはターゲットクラスのライフサイクルイベントやメソッドと組み合わせて実行するメソッドを持たせます。
インターセプタークラスとメソッドはメタデータアノテーションを使用して定義するか、アプリケーションのデプロイメント記述子にインターセプターとターゲットクラスを定義します。
Note: インターセプター定義にデプロイメント記述子を使用するアプリケーションはJava EEサーバ間のポータビリティはありません。
インターセプタークラスやターゲットクラス内のインターセプターメソッドは表 54-の1メタデータアノテーションの一つを付与します。
インターセプターのメタデータアノテーション | 説明 |
---|---|
javax.interceptor.AroundConstruct |
ターゲットクラスのコンストラクタ実行後にコールバックを受けるインターセプターメソッドを指定します。 |
javax.interceptor.AroundInvoke |
そのメソッドをインターセプターメソッドに指定します。 |
javax.interceptor.AroundTimeout |
そのメソッドをEJBタイマーのタイムアウトメソッドにおけるタイムアウトインターセプターに指定します。 |
javax.annotation.PostConstruct |
そのメソッドをpost-constructライフサイクルイベントのインターセプターメソッドに指定します。 |
javax.annotation.PreDestroy |
そのメソッドをpre-destroyライフサイクルイベントのインターセプターメソッドに指定します。 |
54.1.1 Interceptor Classes
インターセプタークラスはjavax.interceptor.Interceptor
アノテーションを付与しても、しなくても、どちらでも構いません。なお、インターセプタークラスはpublicで引数無しのコンストラクタが必須です。
ターゲットクラスは任意の数のインターセプタークラスを関連付けられます。インターセプタークラスの呼び出し順序はjavax.interceptor.Interceptors
アノテーションにおけるインターセプタークラスの定義順になります。ただし、この順序はデプロイメント記述子でオーバーライド可能です。
インターセプタークラスはDIのターゲットになります。インターセプタークラスのインスタンスが生成される時、関連ターゲットクラスのネーミングコンテキストを使用してDIが行われます。DIは@PostConstruct
コールバックが実行される前に行われます。
54.1.2 Interceptor Lifecycle
インターセプタークラスは関連ターゲットクラスと同様のライフサイクルを持ちます。ターゲットクラスのインスタンスが生成されるとき、ターゲットクラスに宣言されたインターセプタークラスのインスタンスも生成されます。ターゲットクラスが複数のインターセプタークラスを宣言している場合、ターゲットクラスのインスタンスが生成される時に各クラスのインスタンスが生成されます。ターゲットクラスのインスタンスとすべてのインターセプタークラスのインスタンスは、@PostConstruct
コールバックが呼び出される前に、完全にインスタンス化されます。また、@PreDestroy
コールバックはターゲットクラスとインターセプタークラスのインスタンスが破棄される前に呼び出されます。
54.1.3 Interceptors and CDI
CDIはJava EEインターセプターの基本機能に基づいて作られています。インターセプターバインディングタイプを含むCDIインターセプターの詳細についてはUsing Interceptors in CDI Applicationsを参照してください。
54.2 Using Interceptors
インターセプターを定義するには、ターゲットクラス内で表 54-1のインターセプターメタデータアノテーションを使うか、別のインターセプタークラスを作成します。以下のコードはターゲットクラス内で@AroundTimeout
インターセプターメソッドを宣言しています。
@Stateless public class TimerBean { ... @Schedule(minute="*/1", hour="*") public void automaticTimerMethod() { ... } @AroundTimeout public void timeoutInterceptorMethod(InvocationContext ctx) { ... } ... }
インターセプタークラスを使用する場合、ターゲットクラスのクラスかメソッドレベルで一つ以上のインターセプターを宣言するのにjavax.interceptor.Interceptors
アノテーションを使用します。以下のコードはクラスレベルでインターセプターを宣言しています。
@Stateless @Interceptors({PrimaryInterceptor.class, SecondaryInterceptor.class}) public class OrderBean { ... }
以下のコードはメソッドレベルでインターセプタークラスを宣言しています。
@Stateless public class OrderBean { ... @Interceptors(OrderInterceptor.class) public void placeOrder(Order order) { ... } ... }
54.2.1 Intercepting Method Invocations
@AroundInvoke
でマネージドオブジェクト用のインターセプターメソッドを指定します。around-invokeインターセプターはクラスに一つだけ作成できます。around-invokeインターセプターメソッドは以下のような形式を取ります。
@AroundInvoke visibility Object method-name(InvocationContext) throws Exception { ... }
たとえば、
@AroundInvoke public void interceptOrder(InvocationContext ctx) { ... }
Around-invokeインターセプターメソッドの可視性は、public, private, protected, packageレベルが可能で、staticやfinalは禁止です。
Around-invokeインターセプターは、インターセプト対象のターゲットメソッドが呼び出すコンポーネントやリソースを呼び出し可能で、ターゲットメソッドと同一のセキュリティとトランザクションコンテキストを持ちます。また、ターゲットメソッドと同一のJava VMコールスタックで実行できます。
Around-invokeインターセプターは実行時例外とターゲットメソッドのthrows
節の例外をスロー可能です。例外をcatchするか握りつぶし、それからInvocationContext.proceed
を呼ぶことで復帰が可能です。
54.2.1.1 Using Multiple Method Interceptors
ターゲットメソッドやクラスに複数のインターセプターを宣言するには@Interceptors
を使います。
@Interceptors({PrimaryInterceptor.class, SecondaryInterceptor.class, LastInterceptor.class}) public void updateInfo(String info) { ... }
@Interceptors
のインターセプターの定義順序がインターセプターが呼び出される順序になります。
デプロイメント記述子に複数のインターセプターを定義することも可能です。デプロイメント記述子のインターセプターの定義順序がインターセプターが呼び出される順序になります。
... <interceptor-binding> <target-name>myapp.OrderBean</target-name> <interceptor-class>myapp.PrimaryInterceptor.class</interceptor-class> <interceptor-class>myapp.SecondaryInterceptor.class</interceptor-class> <interceptor-class>myapp.LastInterceptor.class</interceptor-class> <method-name>updateInfo</method-name> </interceptor-binding> ...
チェーンの次のインターセプターへ明示的に制御を渡すには、InvocationContext.proceed
メソッドを呼びます。
データはインターセプターをまたがって共有可能です。
- 同一の
InvocationContext
インスタンスが、個々のターゲットメソッドにおけるインターセプターチェーン上のインターセプターそれぞれの入力パラメータとして、渡されます。インターセプターメソッドをまたがるデータの受け渡しにはInvocationContext
インスタンスのcontextData
プロパティを使用します。contextData
プロパティはjava.util.Map<String, Object>
オブジェクトです。contextData
に格納されたデータは、それ以降のインターセプターチェーンのインターセプターメソッドで利用可能です。 contextData
に格納されたデータは異なるターゲットクラスのメソッド実行では共有できません。つまり、ターゲットクラスのそれぞれのメソッド実行ごとに、異なるInvocationContext
オブジェクトが生成されます。
54.2.1.2 Accessing Target Method Parameters from an Interceptor Class
ターゲットメソッドの引数にアクセスしたり修正するために、around-invokeメソッドに渡されるInvocationContext
インスタンスを使えます。InvocationContext
のparameters
プロパティはObject
インスタンスの配列で、これはターゲットメソッド引数の定義順に対応しています。たとえば以下のターゲットメソッドでは、PrimaryInterceptor
のaround-invokeインターセプターメソッドに渡されるInvocationContext
インスタンスのparameters
プロパティは、二つのString
オブジェクト(firstName
とlastName
)とDate
オブジェクトのObject
配列になります。
@Interceptors(PrimaryInterceptor.class) public void updateInfo(String firstName, String lastName, Date date) { ... }
InvocationContext.getParameters
とInvocationContext.setParameters
を使用して引数にアクセスしたり修正することが可能です。
54.2.2 Intercepting Lifecycle Callback Events
ライサイクルコールバックイベント(around-construct, post-construct, and pre-destroy)用のインターセプターは、インターセプタークラスかターゲットクラスに定義可能です。ターゲットクラスのコンストラクタ実行をインターセプトするメソッドにはjavax.interceptor.AroundConstruct
を指定します。post-constructライフクサイクルイベントインターセプターのメソッドにはjavax.annotation.PostConstruct
を指定します。pre-destroyライフクサイクルイベントインターセプターのメソッドにはjavax.annotation.PreDestroy
を指定します。
ターゲットクラス内に定義するライフクサイクルイベントインターセプターは以下の形式を取ります。
void method-name() { ... }
例としては、
@PostConstruct void initialize() { ... }
インターセプタークラスに定義するライフクサイクルイベントインターセプターは以下の形式を取ります。
void method-name(InvocationContext) { ... }
例としては、
@PreDestroy void cleanup(InvocationContext ctx) { ... }
ライフクサイクルインターセプターのメソッドの可視性は、public, private, protected, packageレベルが可能で、staticやfinalは禁止です。ライフクサイクルインターセプターは実行時例外はスロー可能ですが、チェック例外はスロー出来ません。
ライフクサイクルインターセプターのメソッドは不明なセキュリティおよびトランザクションコンテキストで呼ばれます。Java EEアプリケーションにポータビリティを持たせるには、ライフサイクルイベントインターセプターのメソッドがセキュリティやトランザクションコンテキストにアクセスするという仮定をすべきではありません。あるクラスのライフサイクルイベント(post-createとpre-destroy)それぞれにつき、一つだけインターセプターメソッドが指定可能です。
54.2.2.1 Using AroundConstruct Interceptor Methods
@AroundConstruct
メソッドはターゲットクラスのコンストラクタ実行をインターセプトします。@AroundConstruct
を付与するメソッドはインターセプタークラスかインターセプタークラスの親クラスでだけ定義可能です。ターゲットクラス内では@AroundConstruct
を使用できません。
@AroundConstruct
メソッドは、ターゲットクラスに関連付けられているすべてのインターセプターのDIを完了したあとに、呼び出されます。ターゲットクラスが生成されて、すべての@AroundConstruct
メソッドがInvocation.proceed
メソッドを呼び出し終えたあとに、ターゲットクラスのコンストラクタインジェクションが実行されます。ターゲットクラスのDIが完了すると、@PostConstruct
コールバックが呼び出されます。
@AroundConstruct
メソッドは、Invocation.proceedを呼び出した後は、
InvocationContext.getTarget```を呼ぶことで新規作成されたターゲットインスタンスにアクセス可能です。
注意: @AroundConstruct
メソッドからターゲットインスタンスのメソッドを呼ぶことは危険です。その理由は、ターゲットインスタンスのDIが完了していない可能性があるためです。
@AroundConstruct
メソッドはターゲットインスタンスを生成するためにInvocation.proceed
を呼ぶ必要があります。もし@AroundConstruct
メソッドがInvocation.proceed
を呼ばない場合、ターゲットインスタンスは生成されません。
54.2.2.2 Using Multiple Lifecycle Callback Interceptors
ターゲットクラスに複数のライフサイクルインターセプターを定義するには、@Interceptors
に複数のインターセプターを指定します。
@Interceptors({PrimaryInterceptor.class, SecondaryInterceptor.class, LastInterceptor.class}) @Stateless public class OrderBean { ... }
InvocationContext
のcontextData
プロパティに格納されるデータは異なるライフサイクルイベントにまたがっての共有はできません。
54.2.3 Intercepting Timeout Events
EJBタイマーサービスのタイムアウトメソッド用のインターセプターを、@AroundTimeout
を使用してターゲットクラスのメソッドもしくはインターセプタークラスに定義できます。一つのクラスには一つだけ@AroundTimeout
を定義できます。
タイムアウトインターセプターは以下の形式をとります。
Object method-name(InvocationContext) throws Exception { ... }
例としては、
@AroundTimeout protected void timeoutInterceptorMethod(InvocationContext ctx) { ... }
タイムアウトインターセプターメソッドは、public, private, protected, packageを取ることができ、staticもしくはfinalは宣言禁止です。
タイムアウトインターセプターはターゲットのタイムアウトメソッドが呼び出すコンポーネントやリソースを呼ぶことが可能で、インターセプターはターゲットメソッドと同一のトランザクションとセキュリティコンテキストで実行されます。
タイムアウトインターセプターは、InvocationContext
インスタンスのgetTimer
メソッド経由でターゲットタイムアウトメソッドに紐付いているタイマーオブジェクトにアクセスが可能です。
54.2.3.1 Using Multiple Timeout Interceptors
ターゲットクラスに複数のタイムアウトインターセプターを定義するには、クラスレベルの@Interceptors
に@AroundTimeout
インターセプターメソッドを含むインターセプタークラスを指定します。
ターゲットクラスがインターセプタークラスにタイムアウトインターセプターを指定し、ターゲットクラス自身にも@AroundTimeout
インターセプターメソッドを持つ場合、まず最初にインターセプタークラスのタイムアウトインターセプターが呼ばれ、それからターゲットクラスに定義されたタイムアウトインターセプターが呼ばれます。たとえば、以下のようにPrimaryInterceptor
とSecondaryInterceptor
クラスが両方ともタイムアウトインターセプターメソッドだとすると、
@Interceptors({PrimaryInterceptor.class, SecondaryInterceptor.class}) @Stateful public class OrderBean { ... @AroundTimeout private void last(InvocationContext ctx) { ... } ... }
最初にPrimaryInterceptor
のタイムアウトインターセプターが呼び出され、次にSecondaryInterceptor
が呼び出され、最後にターゲットクラスのlast
メソッドが呼び出されます。
54.2.4 Binding Interceptors to Components
インターセプターバインディングタイプはコンポーネントと指定のインターセプターを関連付けるために使用します。インターセプターバインディングタイプは通常、インターセプターのターゲットを指定するカスタムのランタイムアノテーションタイプです。カスタムアノテーション定義にjavax.interceptor.InterceptorBinding
を使用し、@Target
に、TYPE
(クラスレベルインターセプター)、METHOD
(メソッドレベルインターセプター)、CONSTRUCTOR
(around-constructインターセプター)、その他有効なターゲット、を一つ以上設定します。
@InterceptorBinding @Target({TYPE, METHOD}) @Retention(RUNTIME) @Inherited pubic @interface Logged { ... }
インターセプターバインディングタイプは他のインターセプターバインディングタイプにも適用可能です。
@Logged @InterceptorBinding @Target({TYPE, METHOD}) @Retention(RUNTIME) @Inherited public @interface Secured { ... }
54.2.4.1 Declaring the Interceptor Bindings on an Interceptor Class
インターセプターのクラスにインターセプターバインディングタイプのアノテーションと、クラスとインターセプターバインディングを関連付けるために@Interceptor
を付与します。
@Logged @Interceptor public class LoggingInterceptor { @AroundInvoke public Object logInvocation(InvocationContext ctx) throws Exception { ... } ... }
インターセプタークラスは複数のインターセプターバインディングを宣言可能で、一つ以上のインターセプタークラスがインターセプターバインディングタイプを宣言可能です。
インターセプタークラスがライフサイクルコールバックをインターセプトする場合、Target(TYPE)
のみにするか、@AroundConstruct
ライフサイクルコールバックの場合にはTarget(CONSTRUCTOR)
のみが宣言可能です。
54.2.4.2 Binding a Component to an Interceptor
インターセプターバインディングタイプはターゲットコンポーネントのクラス・メソッド・コンストラクタにアノテーションを付与します。インターセプターバインディングタイプは@Interceptor
と同様なルールを使用して適用します。
@Logged public class Message { ... @Secured public void getConfidentialMessage() { ... } ... }
コンポーネントがクラスレベルのインターセプターバインディングを持つ場合、コンポーネントは非final
にするか、非static
非private final
メソッドにする必要があります*1。
もし非static
非private
メソッドにインターセプターバインディングが適用されている場合、メソッドは非final
かつコンポーネントクラスは非final
でなければなりません。
54.2.5 Ordering Interceptors
複数のインターセプターが実行される順序は以下のルールによって決定されます。
- 最初に実行されるのは、デプロイメント記述子に定義されているデフォルトインターセプターです。デフォルトインターセプターは実行順序を指定したり、アノテーションで指定した順序をオーバーライドすることが出来ます。デフォルトインターセプターの実行順序はデプロイメント記述子の定義順になります。
@Interceptors
に指定したインターセプタークラスの順序がインターセプタークラスが実行される順序になります。@Interceptors
内に指定したインターセプターの@Priority
設定は無視されます。- インターセプタークラスが親クラスを持つ場合、先にスーパークラスで定義されたインターセプターが実行され、最も上位のスーパークラスから実行されます。
- インターセプタークラスは
javax.annotation.Priority
でインターセプターメソッドのプライオリティに値を設定可能です。 - インターセプタークラス内に定義されたインターセプターが実行された後に、ターゲットクラスのコンストラクタ・around-invoke・around-timeoutインターセプターが
@Interceptors
内のインターセプターと同じ順序で実行されます。 - ターゲットクラスがスーパークラスを持つ場合、まずスーパークラスに定義されているインターセプターが実行され、最も上位のスーパークラスから実行されます。
@Priority
アノテーションは要素の値にint
を取ります。関連インターセプターのプライオリティの高低を設定します。
Note: インターセプターの実行順序を同一値にする場合は実装依存になります。
javax.interceptor.Interceptor.Priority
クラスには表 54-2のプライオリティ定数値が定義されています。
表 54-2 インターセプタープライオリティの定数値
プライオリティ定数値 | 値 | 説明 |
---|---|---|
PLATFORM_BEFORE |
0 | Java EEプラットフォームが定義するインターセプターで、呼び出しチェーンの早期段階で呼び出されるよう指定するもので、この値はPLATFORM_BEFORE とLIBRARY_BEFORE の間を使用すべきです。このインターセプターが最も高いプライオリティになります。 |
LIBRARY_BEFORE |
1000 | 拡張ライブラリが定義するインターセプターはインターセプターチェーンの早期段階で実行されるべきで、この値はLIBRARY_BEFORE とAPPLICATION の間を使用すべきです。 |
APPLICATION |
2000 | アプリケーションが定義するインターセプターはAPPLICATION とLIBRARY_AFTER の間を使用しべきです。 |
LIBRARY_AFTER |
3000 | 拡張ライブラリが定義する低プライオリティインターセプターはLIBRARY_AFTER とPLATFORM_AFTER の間を使用すべきです。 |
PLATFORM_AFTER |
4000 | Java EEプラットフォームが定義する低プライオリティインターセプターはPLATFORM_AFTER よりも高い値を使用すべきです。 |
Note: 負数はInterceptors仕様が将来に向けて予約しているため使用すべきではありません。
以下のコードはアプリケーション定義のインターセプターにプライオリティ定数値を使用する方法です。
@Interceptor @Priority(Interceptor.Priority.APPLICATION+200) public class MyInterceptor { ... }
54.3 The interceptor Example Application
interceptor
サンプルはインターセプタークラスの使用方法を紹介しており、ステートレスセッションビーンで@AroundInvoke
インターセプターメソッドを使用しています。
HelloBean
ステートレスセッションビーンは、文字列の取得と修正を行うgetName
とsetName
の二つのビジネスメソッドからなるシンプルなEJBです。setName
ビジネスメソッドにはインターセプタークラスHelloInterceptor
を指定している@Interceptors
が付与されています。
@Interceptors(HelloInterceptor.class) public void setName(String name) { this.name = name; }
HelloInterceptor
クラスは@AroundInvoke
インターセプターメソッドmodifyGreeting
が定義されており、HelloBean.setName
に渡された文字列を小文字に変換します。
@AroundInvoke public Object modifyGreeting(InvocationContext ctx) throws Exception { Object[] parameters = ctx.getParameters(); String param = (String) parameters[0]; param = param.toLowerCase(); parameters[0] = param; ctx.setParameters(parameters); try { return ctx.proceed(); } catch (Exception e) { logger.warning("Error calling ctx.proceed in modifyGreeting()"); return null; } }
InvocationContext.getParameters
メソッドを呼ぶことでObject
の配列にHelloBean.setName
の引数が取得できます。setName
の引数は一つだけなので、配列の要素は最初の一つだけです。文字列を小文字にしてparameters
配列に代入して、InvocationContext.setParameters
に渡します。セッションビーンに制御を戻すには、InvocationContext.proceed
を呼びます。
interceptor
のユーザーインターフェースはJSFで二つのFaceletsビューから構成されます。index.xhtml
には氏名を入力するフォームがあり、response.xhtml
は結果を表示します。
54.3.1 Running the interceptor Example
- GlassFishサーバが実行中なことを確認(Starting and Stopping GlassFish Serverを参照)
- FileメニューからOpen Projectを選択
- Open Projectダイアログボックスから以下のディレクトリに移動。
tut-install/examples/ejb
*2 interceptor
フォルダを選択してOpen Projectをクリック。- Projectsタブで
interceptor
プロジェクトを右クリックしてRunを選択。
コンパイル・デプロイ・interceptor
が実行され、webブラウザで以下のURLを開きます。
http://localhost:8080/interceptor/
- フォームに名前を入力してSubmitをクリック。
名前がHelloInterceptor
クラスで定義されたメソッドインターセプターによって小文字に変換されます。
54.3.1.2 To Run the interceptor Example Using Maven
- GlassFishサーバが実行中なことを確認(Starting and Stopping GlassFish Serverを参照)
- ターミナルで以下に移動:
tut-install/examples/ejb/interceptor/
- 以下のコマンドを実行。
mvn install
このコマンドはアプリケーションをビルドしてパッケージングしてtarget
ディレクトリにinterceptor.war
を生成します。それから、GlassFish ServerにWARファイルをデプロイします。
http://localhost:8080/interceptor/
- フォームに名前を入力してSubmitをクリック。
名前がHelloInterceptor
クラスで定義されたメソッドインターセプターによって小文字に変換されます。
関連リンク
- The Java EE 7 Tutorialのテキトー翻訳まとめ - Qiita - Java EE 7 Tutorialのうち、自分がテキトー翻訳したものの一覧
*1:it must not be final or have any non-static, non-private final methods. 上手く訳せず。it must ... haveで良いのか?
*2:https://java.net/projects/javaeetutorial/sources/svn/show/trunk/examples/ejb