インターセプタはJavaEEの機能の1つで、メソッドの前後で任意の処理を実行させることができる。「intercept」は「横取りする」という意味がある。
インターセプタを利用するためには以下3つの作業が必要となる。
インターセプタは横断的な処理を実施したい場合に有効である。最もよく使われるのはログ出力である。 昔はメソッドの開始や終了時にstart、endなどのログを直接記述していたが、インターセプタを利用することで、これらの記述をメソッド本体と切り離し、裏で一カ所に記述すればよくなった。
実際にメソッドの開始・終了やメソッドシグニチャ、パラメータ値、戻り値をコンソールに出力するサンプルを作成する。
まずアノテーションを作成する。
package interceptor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;
/** ログを出力するアノテーション */
@Inherited
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Logging{
}
クラス宣言は「@interface」とする。クラス名(Logging)がそのままアノテーション名となる。
アノテーションは以下4つを指定する。
@Inherited
アノテーションを継承させるための指定。例えばアノテーションがAクラスで付与されていると、Aクラスを継承したBクラスでもアノテーションが有効となる。 子クラスから見れば親クラスでアノテーションが付与されていれば、自分で付与する必要はなくなる。
@InterceptorBinding
インターセプタと被インターセプタをバインドする役目を持っていることを示すもの。簡単に言うとインターセプタで使用されるアノテーションであることを示すもの。
@Retention
アノテーションをどこまで記憶させるのかを指定するもの。「Retention」は「記憶」「保持」という意味がある。パラメータとして以下3つの指定が可能。 いずれも「java.lang.annotation.RetentionPolicy」の定数。
定数名 | 説明 |
---|---|
SOURCE | ソースにしか記憶しない。(推測するに@seeのようにコメント的なものに使う?) |
CLASS | クラスファイル内に記憶する。リフレクションでは参照できない。(未指定の場合のデフォルト) |
RUNTIME | JVMに記憶する。リフレクションで参照できる。 |
@Target
アノテーションを付与できる場所の指定。以下が指定できる。いずれも「java.lang.annotation.ElementType」の定数。 アノテーションはクラスやメソッドに付けることが多いため、「TYPE」や「METHOD」がよく使われる。
定数名 | 説明(Oracle Javadocより) |
---|---|
TYPE | クラス、インタフェース(注釈型を含む)、またはenum宣言 |
FIELD | フィールド宣言(enum定数を含む) |
METHOD | メソッド宣言 |
PACKAGE | パッケージ宣言 |
PARAMETER | 仮パラメータ宣言 |
TYPE_PARAMETER | 型パラメータ宣言 |
CONSTRUCTOR | コンストラクタ宣言 |
ANNOTATION_TYPE | 注釈型宣言 |
LOCAL_VARIABLE | ローカル変数宣言 |
TYPE_USE | 型の使用 |
次にインターセプタを作成する。
package interceptor;
import java.lang.reflect.Method;
import javax.annotation.Priority;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
/** ログを出力するインターセプタ */
@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
@Logging
public class LoggingInterceptor{
/** ログを出力するメソッド */
@AroundInvoke
public Object log(InvocationContext ic) throws Exception{
// メソッド情報取得
Method method = ic.getMethod();
// メソッド開始出力
System.out.println("start:" + method);
// メソッドパラメータ出力
for(Object parameter : ic.getParameters()) {
System.out.println("parameter:" + parameter);
}
// メソッド実行
Object result = ic.proceed();
// メソッド戻り値出力
System.out.println("return:" + result);
// メソッド終了出力
System.out.println("end:" + method);
return result;
}
}
①クラス宣言に以下のアノテーションを付与する。
@Interceptor
インターセプタであることを示すもの。
@Priority
インターセプタが複数ある場合に、実行順の優先度を示すもの。beans.xmlでも指定ができる。パラメータとして以下の指定が可能。 全て「javax.interceptor.Interceptor.Priority」の定数。定数の値はint型であり、値が小さいほど先に実行される。 「APPLICATION + 10」のように定数に加算したり、定数を使用せず直接数値を記述することも可能。通常は2000~2999の間になるように指定する。
定数名 | 値 | 説明 |
---|---|---|
PLATFORM_BEFORE | 0 | JavaEEのインターセプタ |
LIBRARY_BEFORE | 1000 | 拡張ライブラリのインターセプタ |
APPLICATION | 2000 | アプリケーションのインターセプタ |
LIBRARY_AFTER | 3000 | アプリケーションの後に実行する拡張ライブラリのインターセプタ |
PLATFORM_AFTER | 4000 | アプリケーションの後に実行するJavaEEのインターセプタ |
@Logging
自作したアノテーション。Logging.javaを作成したため、今回の場合「@Logging」となる。
②メソッドを作成する。
@AroundInvoke
public Object log(InvocationContext ic) throws Exception{
任意の名前でメソッドを作成し、@AroundInvokeアノテーションを付与する。
メソッドの引数はInvocationContext型、戻り値はObject型とする。
引数のInvocationContextにはインターセプトされたメソッドの情報などが入っている。 例えば、InvocationContext#getMethod()でjava.lang.reflect.Methodクラスが取得できる。
戻り値は実メソッドで返却させたいを戻り値を返却する。加工しないでそのまま返却する場合は、InvocationContext#proceed()の戻り値をそのまま返却する。
InvocationContextクラスのproceedメソッドは実メソッドを呼び出す。
Object result = ic.proceed();
このproceedの前後に処理を記述することがインターセプタの役割である。今回は、メソッドの開始・終了およびパラメータ値・戻り値をコンソールに出力している。
なお実メソッドで発生した例外は、そのままproceed()からスローされてくるため、ハンドリングしたい場合は、proceed()をtry-catchで括るとよい。 但し、実メソッドの動作を変更させたくない場合は、同じ例外をリスローすること。
インターセプタの実行確認をするためにサンプルソースを作成する。以下3つを作成。
今回はビジネスロジック(TestLogic.java)の2メソッド(execute、execute2)をインターセプトしてみる。
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:jsf="http://xmlns.jcp.org/jsf">
<h:head>
<title>Javaee Test</title>
</h:head>
<h:body>
<h:form id="frm">
<!-- テキストフィールド -->
<input type="text" jsf:value="#{interceptorTestBean.value}" />
<!-- 送信ボタン -->
<h:commandButton value="送信" action="#{interceptorTestBean.send()}" />
<!-- 画面入力値を表示 -->
<h:outputText value="#{interceptorTestBean.value}" />
</h:form>
</h:body>
</html>
InterceptorTestBean.java
package interceptor;
import java.io.Serializable;
import javax.faces.view.ViewScoped;
import javax.inject.Inject;
import javax.inject.Named;
/**
* 管理Beanクラス
*/
@Named
@ViewScoped
public class InterceptorTestBean implements Serializable{
/** シリアルバージョンUID */
private static final long serialVersionUID = 1L;
/** ロジッククラス */
@Inject
private TestLogic logic;
/** 画面入力値 */
private String value;
/** 画面入力値の設定 */
public void setValue(String value){
this.value = value;
}
/** 画面入力値の取得 */
public String getValue(){
return value;
}
/** イベントメソッド */
public void send(){
logic.execute(value);
logic.execute2(value);
}
}
TestLogic.java
package interceptor;
import javax.enterprise.context.RequestScoped;
/** ロジッククラス */
@RequestScoped
@Logging
public class TestLogic{
/** メソッド1 */
public int execute(String value){
System.out.println("TestLogic execute");
return value.length();
}
/** メソッド2 */
public void execute2(String value){
System.out.println("TestLogic execute2");
}
}
TestLogic.javaのクラス宣言に@Loggingを記述している。 これによりTestLogicクラスの全てのメソッドに適用される(但し、内部的に呼び出したメソッドには適用されないなどの制限あり)。
メソッド単位で付与したい場合は、メソッド宣言の上部に@Loggingを記述すればよい。
@Logging
public int execute(String value){
なお、Logging.javaの@Targetアノテーションでは「TYPE」と「METHOD」のみ指定しているため、フィールドなどに@Loggingは使用できない。
XHTMLから値を入力して、サンプルプログラムを実行するとコンソールへの出力結果は以下となる。
start:public int interceptor.TestLogic.execute(java.lang.String)
parameter:hallo
TestLogic execute
return:5
end:public int interceptor.TestLogic.execute(java.lang.String)
start:public void interceptor.TestLogic.execute2(java.lang.String)
parameter:hallo
TestLogic execute2
return:null
end:public void interceptor.TestLogic.execute2(java.lang.String)
execute()とexecute2()の2メソッド呼んでいるが、その前後でインターセプタが出力した内容が表示されていることがわかる。
なおメソッドの戻り値がvoidの場合はproceedメソッドからnullが返却される。