技術空間

JavaEE

インターセプタでメソッドの前後に処理を入れる


TOP > JavaEE > インターセプタでメソッドの前後に処理を入れる



■インターセプタとは

インターセプタはJavaEEの機能の1つで、メソッドの前後で任意の処理を実行させることができる。「intercept」は「横取りする」という意味がある。

インターセプタを利用するためには以下3つの作業が必要となる。

インターセプタは横断的な処理を実施したい場合に有効である。最もよく使われるのはログ出力である。 昔はメソッドの開始や終了時にstart、endなどのログを直接記述していたが、インターセプタを利用することで、これらの記述をメソッド本体と切り離し、裏で一カ所に記述すればよくなった。

実際にメソッドの開始・終了やメソッドシグニチャ、パラメータ値、戻り値をコンソールに出力するサンプルを作成する。

■サンプル実行
アノテーションを定義する

まずアノテーションを作成する。

Logging.java
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クラスファイル内に記憶する。リフレクションでは参照できない。(未指定の場合のデフォルト)
RUNTIMEJVMに記憶する。リフレクションで参照できる。

@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型の使用


次にインターセプタを作成する。

インターセプタクラスの作成
LoggingInterceptor.java
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_BEFORE0JavaEEのインターセプタ
LIBRARY_BEFORE1000拡張ライブラリのインターセプタ
APPLICATION2000アプリケーションのインターセプタ
LIBRARY_AFTER3000アプリケーションの後に実行する拡張ライブラリのインターセプタ
PLATFORM_AFTER4000アプリケーションの後に実行する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)をインターセプトしてみる。

interceptTest.xhtml
<?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が返却される。

- JavaEEの入門本 -



TOP > JavaEE > インターセプタでメソッドの前後に処理を入れる

Tweet ̃Gg[͂ĂȃubN}[Nɒlj
技術空間