インターセプターとは
アメリカンフットボールの用語に、「インターセプト」があります。パスしたボールが守備側に横取りされることをいうのですが、Struts 2のインターセプターも、アメフトと同じようなイメージです。Actionメソッドが呼び出される前に、複数のインターセプターが割り込み、制御を横取りして実行されます。
図1 インターセプターのフロー図
図中の、ActionProxy、ActionInvocation、Resultは、Struts 2の核となるオブジェクトです。Servletコンテナからのリクエストがあると、(図ではActionProxyが呼ばれる前の処理を省略しています)設定に従ってActionProxyがインターセプターを呼び出します。インターセプターは、ActionInvocationオブジェクトがインスタンスを保持しています。
インターセプターは、その処理のなかで再帰的に次のインターセプターを呼び出します。最後のインターセプターの後は、Actionが実行され、Resultオブジェクトで結果画面の生成をします。それから再び呼び出したインターセプターに制御が戻ってきます。そのときの順番は、最初の逆準となり、図の例では、Interceptor3→Interceptor2→Interceptor1という具合になります。
Struts 2では、その機能の多くがインターセプターとして提供されており、さまざまなインターセプターがあらかじめ用意されています。また、プラグインのように、必要に応じてインターセプターを利用することができます。
このようなアプローチは、アスペクト指向と言えるものです。インターセプターにより、Actionクラスを横断するような汎用的な処理をモジュール化することができます。その結果、Actionクラスは固有の処理に特化できるようになり、Actionクラスとしての役割がシンプルに記述できるようになっています。
定義済みのインターセプターは結構数がそろっているものの、当然Webアプリケーション独自のものを定義したい場合があるでしょう。そんな場合は、インターセプターを自分で記述することができます。本稿では、かんたんなインターセプターを作って、インターセプターの動作を明らかにしていきます。
インターセプター一覧
インターセプター名 | 定義名 | 概要 |
Alias インターセプター | alias | 入力画面のフォームのパラメータ名に別名をつける |
Chaining インターセプター | chain | プロパティの内容を次のActionに引き継ぐ |
Checkbox インターセプター | checkbox | フォームのCheckboxで、checkされていなくても、値がセットされるようにする |
Cookie インターセプター | cookie | cookieデータを管理する |
Conversion Error インターセプター | conversionError | 入力フォームの変換エラーを検出する |
Create Session インターセプター | createSession | セッション情報を作成する |
Debugging インターセプター | debugging | デバッグメッセージを表示する |
Execute and Wait インターセプター | execAndWait | 非同期でActionを実行する |
Exception インターセプター | exception | 例外がスローされたときの処理を設定する |
File Upload インターセプター | fileUpload | ファイルのアップロードを支援する |
I18n インターセプター | i18n | ロケール情報を保持する |
Logger インターセプター | logger | ログ出力を行う |
Message Store インターセプター | store | メッセージの保存と参照を行う |
Model Driven インターセプター | modelDriven | Actionをモデルドリブンで操作する |
Scoped Model Driven インターセプター | scopedModelDriven | Actionをスコープドモデルドリブンで操作する |
Parameters インターセプター | params | リクエストパラメータを制御する |
Prepare インターセプター | prepare | Actionクラスのexecute()メソッドが実行される前に呼び出したい処理を記述する |
Scope インターセプター | scope | Actionクラスのプロパティをsessionやapplicationスコープとして設定する |
Servlet Config インターセプター | servletConfig | session等に情報を保存する |
Static Parameters インターセプター | staticParams | Action単位で静的パラメータを定義する |
Roles インターセプター | roles | JAAS認証時のみActionを実行するようにする |
Timer インターセプター | timer | Actionクラスの実行時間を計測する |
Token インターセプター | token | 2重POSTの禁止処理(リクエスト単位)を行う |
Token Session インターセプター | tokenSession | 2重POSTの禁止処理(セッション単位)を行う |
Validation インターセプター | validation | バリデーション処理を行う |
Workflow インターセプター | workflow | validateメソッドの呼び出しを行う |
Parameter Filter インターセプター | parameterFilter | パラメータの取り消しを行う |
Profiling インターセプター | profiling | プロファイリング機能のON/OFF設定をする |
デフォルトのインターセプター
バリデーション処理では、実装さえすれば、特に呼び出し処理を記述する必要はありませんでした。それは、デフォルトで呼び出されるインターセプターがあり、バリデーションもその一つだからです。
あらかじめ用意されているインターセプターの定義は、デフォルトの設定ファイルのstruts-default.xmlにあります。そこで定義されているインターセプターは30ほどあり、そのうちの半数ほどが、デフォルトのインターセプターとして実行されるようになっています。デフォルトのインターセプターは、多くのアプリケーションで必要となる機能を定義したものです。ちょっとしたアプリケーションなら、デフォルトのインターセプターを変更したり、別のインターセプターを追加したりする必要はないでしょう。
なお、設定ファイルのstruts-default.xmlとは、struts.xmlで「extends="struts-default"」と指定して読み込んでいるデフォルトの設定ファイルのことです。
以下が、struts-default.xmlで、デフォルトのインターセプターを定義している箇所です。
[リスト1]struts-default.xmlの一部
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="debugging"/>
<interceptor-ref name="profiling"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="params">
<param name="excludeParams">dojo\..*</param>
</interceptor-ref>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
</interceptor-stack>
インターセプターに関する設定タグについては、あらためて後述しますが、<interceptor-stack>タグで、インターセプターをグルーピングしています。ここでは、defaultStackというインターセプターのグループ名を指定しています。また、<param name="excludeMethods">とは、指定されたメソッドの場合、そのインターセプターを実行しないという意味です。
アスペクト指向とは
アスペクト指向(Aspect-oriented)とは、オブジェクト指向の問題点を補うために考案された概念です。オブジェクト指向ではうまく表現できない、クラス間を横断するような機能を「アスペクト(英単語としては、外観や様相といった意味)」とみなし、そのアスペクトをモジュール化するプログラミング技法を、アスペクト指向プログラミング(AOP)と呼びます。
まさにインターセプターとは「アスペクト」であり、Struts 2では、インターセプターという手法でアスペクト指向プログラミングを実装していることになります。
独自のインターセプター
では、かんたんなインターセプターを作ってみましょう。自作したインターセプターで動きを確かめてみます。
Interceptor本体
インターセプター本体は、com.opensymphony.xwork2.interceptor.Interceptorインターフェイスを実装する必要があります。Interceptorインターフェイスには、下記のように3つのメソッドが定義してあり、すべて実装しなければなりません。
ただし、com.opensymphony.xwork2.interceptor.AbstractInterceptorというクラスで、Interceptorインターフェイスのinitメソッドとdestroyメソッドが実装されています。内容のない空のメソッドが定義されているだけですが、これを継承してインターセプターを作ると、不要な実装を記述する手間が省けます。
[リスト2]Interceptorインターフェイスの定義
public interface Interceptor extends Serializable {
void destroy();
void init();
String intercept(ActionInvocation invocation) throws Exception;
}
void init()
Servletコンテナ(Tomcat)を起動すると呼ばれます。
void destroy()
Servletコンテナの終了時に呼ばれます。
String intercept(ActionInvocation invocation)
このメソッドに、インターセプターの処理を記述します。パラメータのActionInvocationオブジェクトを使って、リクエストパラメータやセッションにアクセスします。
ActionInvocationオブジェクトは、Actionクラスを実行したときのステータス情報を格納しており、Actionとインターセプターのインスタンスも保持しています。
例えば、 次のようなコードでリクエストパラメータやセッションや取得することができます。
[リスト3]interceptメソッド内に記述するコード
// セッションの取得
// Struts 2では、Mapオブジェクトとしてセッションが取得できる
Map<String, String> session = ActionContext.getContext().getSession();
// HttpServletRequest、HttpSessionの取得
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession session = ServletActionContext.getRequest().getSession();
com.opensymphony.xwork2.ActionContextとは、セッション、リクエストパラメータ、ロケール情報などを保持するクラスで、org.apache.struts2.ServletActionContextは、ActionContextの派生クラスです。
詳細は、オンラインのドキュメントを参照してください。これらのオブジェクトを利用して行えるのは、セッションやリクエストパラメータの取得、Actionオブジェクトの名前の参照、結果コードの参照や変更などです。
Interceptorのサンプルコード
以下は、AbstractInterceptorを継承したインターセプターの例です。このクラスを、連載で使用しているサンプルプロジェクトに組み込んでみましょう。
[リスト4]SimpleInterceptor.java
package part4;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
public class SimpleInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = 1L;
// インターセプターの処理(コンソールに文字列を出力する)
public String intercept(ActionInvocation invocation) throws Exception {
System.out.println("intercept処理");
// 次のインターセプター処理
String result_code = invocation.invoke();
// Result後のインターセプター処理
System.out.println("intercept処理2");
return result_code;
}
}
このインターセプターは、コンソールに文字列を出力するだけのものです。interceptメソッドの最後で、次のインターセプター処理を継続するために、invocation.invoke()メソッドを呼んでいます。そのメソッドを呼び出さず、Actionクラスと同様に、直接、"success"などの結果のコードを返すことができます。その場合は、直後のActionクラスの実行もスキップされることになります。
なお、invocation.invoke()から戻ってきた後は、結果の処理も終わっています。この例では、結果の画面が表示されてから、二度目のSystem.out.printlnの出力処理を行っています。
次に、設定ファイルのstruts.xmlを変更します。
[リスト5]struts.xml(追加)
<interceptors>
<!-- interceptorの定義 -->
<interceptor name="simple" class="part4.SimpleInterceptor"/>
<!-- interceptorをグルーピング -->
<interceptor-stack name="simpleInterceptorStack" >
<!-- 呼び出したい順にinterceptorを記述する -->
<interceptor-ref name="simple" />
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>
<action name="Hello" class="part1.Hello">
<!-- interceptorの指定 -->
<interceptor-ref name="simpleInterceptorStack"/>
<result name="success">/index.jsp</result>
</action>
<interceptors>〜</interceptors>タグ間で、インターセプターの定義をします。そして、<interceptor>タグのname属性で名称、class属性で、インターセプター本体のクラスを指定します。
前述したように、<interceptor-stack>タグはインターセプターのグルーピング指定です。複数のインターセプターをまとめて定義することができます。
Actionクラスごとに指定するインターセプターは、デフォルトのインターセプターとの差分だけという指定ができないので、基本的には常にデフォルトのインターセプター(struts-default.xmlで指定されたdefaultStack)も指定する必要があります。通常、複数のインターセプターを指定することになりますので、グルーピングの設定をしておくと、Actionに適用するインターセプターを1つのグループ名で指定できるようになります。
<action>タグ内の<interceptor-ref>タグで、そのActionに適用するインターセプターを指定します。この指定がない場合は、defaultStackが指定されたことになります。
なお、すべてのActionに適用する場合は、defaultStack自体のグルーピングをstruts.xmlにて上書き設定するか、<default-interceptor-ref>タグを使って、デフォルトのインターセプターを再定義するとよいでしょう(<default-interceptor-ref>タグを使う例は、後述します)。
Actionが実行された後のインターセプター
Actionが実行された後(Resultオブジェクトが処理をする前)にも、インターセプターのメソッドを呼び出すことができます。これは、Action結果のコードを判断して、何らかの処理を行いたい場合や、強制的に結果コードを変更したいときに利用できます。
Actionの後に実行するメソッドは、次のように、ActionInvocationクラスのaddPreResultListenerメソッドを用いて設定します。
[リスト6]変更したinterceptメソッド
public String intercept(ActionInvocation invocation) throws Exception {
System.out.println("intercept処理");
invocation.addPreResultListener(
new PreResultListener() {
public void beforeResult(ActionInvocation actioninvocation, String resultCode) {
System.out.println("Result:"+resultCode);
}
}
);
// 次のインターセプター処理
String result_code = invocation.invoke();
// Result後のインターセプター処理
System.out.println("intercept処理2");
return result_code;
}
com.opensymphony.xwork2.interceptor.PreResultListenerは、beforeResultメソッドのみを定義したインターフェイスです。beforeResultメソッドが、コールバックとして実行されることになりますので、ここに処理を記述します。
beforeResultメソッドには、Actionの結果コードがパラメータで渡されます。上記のコード例は、その結果コードをコンソールに出力するものです。
ログイン認証のインターセプター
今度は、もう少し実用性のあるインターセプターを作ってみることにします。Webアプリケーションでおなじみの「ログイン認証」を実現するインタセプターです。
ログイン認証のフロー
ログイン認証の処理を、Loginインターセプターとして実装します。Loginインターセプターの処理フローは、次の図のようになります。
図4 Loginインターセプターの処理フロー図
サーバ上の、アプリケーション全体のファイル構成は次のようになります。part1〜part3のフォルダにあるファイルに変更はありません。
<ContextRoot>
├ /WEB-INF
│ ├ /classes
│ │ ├ /part4
│ │ │ ├ SimpleInterceptor.class(追加)
│ │ │ ├ LoginInterceptor.class(追加)
│ │ │ └ Login.class(追加)
│ │ └ struts.xml(更新)
│ ├ /lib
│ └ web.xml(変更なし)
├ /part4
│ └ login.jsp(追加)
└ index.jsp(変更なし)
以下が、追加・変更するソース・設定ファイルです。順に説明していくことにしましょう。
§ struts.xml
§ LoginInterceptor.java(ログイン処理のインターセプター)
§ Login.java(ログインActionクラス)
§ login.jsp(ログイン画面)
なお、ログインActionクラスのLogin.java(Login.class)は図にないファイルですが、これは、ログイン画面からsubmitされるActionクラスです。ただ、ログイン処理は、すべてLoginインターセプターで行いますので、Loginインターセプターを起動するためだけの、ダミーのActionクラスです。
struts.xml
struts.xmlには、次の定義を追加します。
[リスト7]struts.xml(追加)
<interceptors>
<!-- interceptorの定義 -->
<interceptor name="simple" class="part4.SimpleInterceptor"/>
<!-- interceptorの定義 -->
<interceptor name="login" class="part4.LoginInterceptor" />
<!-- interceptorをグルーピング -->
<interceptor-stack name="myDefaultStack">
<!-- 呼び出したい順にinterceptorを記述する -->
<interceptor-ref name="simple" />
<interceptor-ref name="login" />
<interceptor-ref name="defaultStack" />
</interceptor-stack>
</interceptors>
<!-- デフォルトinterceptorを再定義 -->
<default-interceptor-ref name="myDefaultStack"/>
<global-results>
<result name="login">/part4/login.jsp</result>
<result name="login-success">/index.jsp</result>
</global-results>
<action name="Login" class="part4.Login">
<result name="success">/index.jsp</result>
</action>
<interceptor>タグで、LoginInterceptorクラスをloginという名前で定義しています。そして、このインターセプターと、先ほどのSimpleInterceptor、およびdefaultStackをまとめて、myDefaultStackというインターセプターグループにします。
<default-interceptor-ref>タグでは、上記のグループを、デフォルトのインターセプターとして再定義しています。
<global-results>タグは、文字通り、すべてのActionクラスの結果を受けることができます。通常は、個別のActionごとに、結果コードの表示先を指定しますが、<global-results>タグにより、すべてのActionクラスの結果を共有したViewを設定できます。
ここでは、未ログインのときの結果コード「login」でログイン画面の呼び出し、ログイン認証が完了したときの結果コード「login-success」で、index.jspを呼び出すようにしています。
LoginInterceptor.java
LoginInterceptorのinterceptメソッドは、次のようになります。
[リスト8]LoginInterceptor.javaの一部
public String intercept(ActionInvocation invocation) throws Exception {
// HttpServletRequest、HttpSessionの取得
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession session = ServletActionContext.getRequest().getSession();
// (1) ログインしていれば、次のインターセプターへ
if ( session.getAttribute("userid") != null &&
session.getAttribute("userid").equals("part4") ){
return invocation.invoke();
}
// (2) リクエストパラメーターのuseridとpasswordを取得
String userid = request.getParameter("userid");
String passwd = request.getParameter("password");
System.out.println(userid);
System.out.println(passwd);
if ( userid != null && passwd != null &&
userid.equals("part4") && passwd.equals("wings") ) {
// (3) 新たなセッションにuseridを設定する
ServletActionContext.getRequest().getSession(true).invalidate();
HttpSession newsession = ServletActionContext.getRequest().getSession(true);
newsession.setAttribute("userid", userid );
return "login-success";
}
return "login";
}
0 件のコメント:
コメントを投稿