初心者
CDI
備忘録
アノテーション
インターセプタ

【CDI】インターセプタ(@Interceptor)

概要

CDI管理Beanのロジック処理における
前後の共通処理部分を切り分けて共通パーツとして扱う仕組みである
@Interceptorの使い方

環境

対象読者

  • CDI初心者

インターセプトとは

アプリケーションのロジックには直接関係しない、前処理や後処理部分を切り離し
共通部品として扱えるようにする仕組みのことを言います。

【 図1.前後処理の共通化 】

インターセプタ1.png

この切り分けた部分を、必要とするロジック達で使いまわします。

使われる場面としては

  • トランザクション管理
  • メソッドの開始・終了ログ

等が多いそうです。

今回は基本的な記述方法や動きだけを確認していきます。

まずはインターセプタの動きを確認する為のCDI管理Bean

InterceptorTestBean.java
package bean;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

import annotation.PracticeInterceptor;

@Named
@ApplicationScoped
public class InterceptorTestBean {

    @PracticeInterceptor
    public void execute() {
        System.out.println("InterceptorTestBean execute() now");
    }
}

index.xhtmlのボタンを押下された際
このInterceptorTestBeanのexecute()メソッドを呼び出します。

そしてこのメソッドが呼び出された際に
共通パーツとして切り分けておいたインターセプタを特定、実行する為に

@PracticeInterceptor

アノテーションを作成、付与します。

作成の記述は以下

PracticeInterceptor.java
package annotation;

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 PracticeInterceptor{}

次にインターセプタの内容です。

TraceInterceptor.java
package interceptor;

import javax.annotation.Priority;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import annotation.PracticeInterceptor;

@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
@PracticeInterceptor
public class TraceInterceptor {

    @AroundInvoke
    public Object obj(InvocationContext ic)throws Exception{

        //メソッド実行前
        System.out.println("start in Interceptor");

        //次のインターセプターチェーンの実行
        Object result = ic.proceed();

        //メソッドの実行後
        System.out.println("end in Interceptor");

        return result;
    }
}

ここのインターセプタにも先ほど作成した@PracticeInterceptorを付与することで、このインターセプタが特定されて実行されます。

最後に、処理を起動する為のViewです。
「インタセプタ確認」ボタンを押下することで処理が走ります。

index.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:jsf="http://xmlns.jcp.org/jsf">
<head>
<title>型解決テスト</title>
</head>
<h:body>
    <h:form id="frm">
        <input jsf:action="#{interceptorTestBean.execute}" type="submit" value="インタセプタ確認" />
    </h:form>
</h:body>
</html>
実行結果(コンソール画面)
2017-07-18T15:25:40.303+0900|情報: start in Interceptor
2017-07-18T15:25:40.303+0900|情報: InterceptorTestBean execute() now
2017-07-18T15:25:40.303+0900|情報: end in Interceptor

インターセプタに関連してくるアノテーション

初めて見るアノテーションが多かったので
上から順に1つ1つ確認していきます。

InterceptorTestBean.java

  • @PracticeInterceptor

 自分で作成したインターセプタバインディング型のアノテーションです。インターセプタバインディング型については後程記述します。

PracticeInterceptor.java

  • @Inherited
  • @InterceptorBinding
  • @Retention(RetentionPolicy.RUNTIME)
  • @Target({ElementType.METHOD,ElementType.TYPE})

 @Retention@Targetに関しては、過去記事にあるので省略します。

@Inherited

 「@Inheritedが付与されたアノテーション」を付与したクラスが継承された際、子クラスにアノテーションも継承させます。
(メタアノテーション)
※要検証

@InterceptorBinding

 インターセプタの胆アノテーション。
このメタアノテーションが付与されたアノテーションをインターセプタ実装クラスとインターセプト対象に付与することで、紐付を行います。
出来上がったユーザ定義アノテーションは読んで字のごとく
インターセプタバインディング型
(インターセプタを紐付るよー型)
と呼ばれます。

TraceInterceptor.java

  • @Interceptor
  • @Priority(Interceptor.Priority.APPLICATION)
  • @AroundInvoke

@Interceptor

 このアノテーションを付与することで、付与されたクラスはインターセプタと認識され、CDI管理Beanとしてコンテナに管理されます。

@Priority(Interceptor.Priority.APPLICATION)

 インターセプタの優先順位を示します。
()内の数値が小さい順に優先されます。以下は用意されている定数です。

  • PLATFORM_BEFORE = 0
  • LIBRARY_BEFORE = 1000
  • APPLICATION = 2000
  • LIBRARY_AFTER = 3000
  • PLATFORM_AFTER = 4000

@AroundInvoke

 インターセプト実装メソッドに付与するアノテーションです。
付与されたメソッドは実行時に引数として InvocationContextインスタンスを設定します。

InvocationContextが持っているメソッドとして
proceed()
があり、このメソッドを呼び出すことでインターセプト対象とした本来のメソッドを実行します。

共通部分を分けた際の
前処理は .proceed() の前に
後処理は .proceed() の後に
記述することになります。

※戻り値はQbject型になります。

メソッドではなくクラスにインターセプト

先ほどはメソッドをインターセプトの対象に選びました。
今度はクラスをインターセプトの対象に選んでみます。

InterceptorTestBean.java
package bean;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

import annotation.PracticeInterceptor;

@Named
@ApplicationScoped
@PracticeInterceptor
public class InterceptorTestBean {


    public void execute1() {
        System.out.println("InterceptorTestBean execute1() now");
    }
    public void execute2() {
        System.out.println("InterceptorTestBean execute2() now");
    }
    public void execute3() {
        System.out.println("InterceptorTestBean execute3() now");
    }
}

メソッドから@PracticeInterceptorアノテーションを取り除きクラスに付与。
そのうえでメソッドを3つに増やしました。

処理を起動する為のViewもボタンを3つに増やします。

index.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:jsf="http://xmlns.jcp.org/jsf">
<head>
<title>型解決テスト</title>
</head>
<h:body>
    <h:form id="frm">
        <input jsf:action="#{interceptorTestBean.execute1}" type="submit" value="インタセプタ1確認" />
        <input jsf:action="#{interceptorTestBean.execute2}" type="submit" value="インタセプタ2確認" />
        <input jsf:action="#{interceptorTestBean.execute3}" type="submit" value="インタセプタ3確認" />
    </h:form>
</h:body>
</html>

インターセプタView.png

左から順にボタンを3つ押した実行結果

実行結果(コンソール画面)
2017-07-18T15:30:17.115+0900|情報: start in Interceptor
2017-07-18T15:30:17.115+0900|情報: InterceptorTestBean execute1() now
2017-07-18T15:30:17.115+0900|情報: end in Interceptor
2017-07-18T15:30:19.218+0900|情報: start in Interceptor
2017-07-18T15:30:19.218+0900|情報: InterceptorTestBean execute2() now
2017-07-18T15:30:19.218+0900|情報: end in Interceptor
2017-07-18T15:30:21.507+0900|情報: start in Interceptor
2017-07-18T15:30:21.508+0900|情報: InterceptorTestBean execute3() now
2017-07-18T15:30:21.508+0900|情報: end in Interceptor

InterceptorTestBeanクラスのすべてのメソッドに
インターセプトが適用されている事が確認できました。

1つのメソッドに複数のインターセプト

先述した@Priorityを用いて優先順位付けを行った上で
1つのメソッドに複数インターセプトを適用してみます。

1つ目のインターセプタ(優先度:高)
@Priorityの数値 APPLICATION

TraceInterceptor1.java
package interceptor;

import javax.annotation.Priority;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import annotation.PracticeInterceptor;

@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
@PracticeInterceptor
public class TraceInterceptor1 {

    @AroundInvoke
    public Object obj(InvocationContext ic)throws Exception{

        //メソッド実行前
        System.out.println("start in Interceptor1");

        //次のインターセプターチェーンの実行
        Object result = ic.proceed();

        //メソッドの実行後
        System.out.println("end in Interceptor1");

        return result;
    }
}

2つ目のインターセプタ(優先度:低)
@Priorityの数値 APPLICATION + 10

TraceInterceptor2.java
package interceptor;

import javax.annotation.Priority;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import annotation.PracticeInterceptor;

@Interceptor
@Priority(Interceptor.Priority.APPLICATION + 10)
@PracticeInterceptor
public class TraceInterceptor2 {

    @AroundInvoke
    public Object obj(InvocationContext ic)throws Exception{

        //メソッド実行前
        System.out.println("start in Interceptor2");

        //次のインターセプターチェーンの実行
        Object result = ic.proceed();

        //メソッドの実行後
        System.out.println("end in Interceptor2");

        return result;
    }
}

以上の構成に書き換えて execute1() を呼び出し。
(View 左ボタン)

実行結果(コンソール画面)
2017-07-18T15:59:49.910+0900|情報: start in Interceptor1
2017-07-18T15:59:49.910+0900|情報: start in Interceptor2
2017-07-18T15:59:49.910+0900|情報: InterceptorTestBean execute1() now
2017-07-18T15:59:49.910+0900|情報: end in Interceptor2
2017-07-18T15:59:49.910+0900|情報: end in Interceptor1

このような結果になります。
優先度順で挟み込む形です。

インターセプタ2.png

まだログやトランザクションは触ったことがないので実際に実用するとどうなるのかはピンときませんが

どういった動きをしているのかは確認できました。