#概要
CDI管理Beanのロジック処理における
前後の共通処理部分を切り分けて共通パーツとして扱う仕組みである
@Interceptorの使い方
#環境
- IDE : Eclipse 4.7 Oxygen
- APサーバ : GlassFish 4.1.1
- JDK 1.8
- OS : Windows7
- プロジェクトの設定
#対象読者
- CDI初心者
#インターセプトとは
アプリケーションのロジックには直接関係しない、前処理や後処理部分を切り離し
共通部品として扱えるようにする仕組みのことを言います。
この切り分けた部分を、必要とするロジック達で使いまわします。
使われる場面としては
- トランザクション管理
- メソッドの開始・終了ログ
等が多いそうです。
今回は基本的な記述方法や動きだけを確認していきます。
まずはインターセプタの動きを確認する為のCDI管理Bean
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
アノテーションを作成、付与します。
作成の記述は以下
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{}
次にインターセプタの内容です。
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です。
「インタセプタ確認」ボタンを押下することで処理が走ります。
<?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型になります。
###メソッドではなくクラスにインターセプト
先ほどはメソッドをインターセプトの対象に選びました。
今度はクラスをインターセプトの対象に選んでみます。
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つに増やします。
<?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>
左から順にボタンを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
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
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
このような結果になります。
優先度順で挟み込む形です。
まだログやトランザクションは触ったことがないので実際に実用するとどうなるのかはピンときませんが
どういった動きをしているのかは確認できました。