技術空間

Spring

Interceptorでコントローラメソッドの前後に処理を入れる


TOP > Spring > Interceptorでコントローラメソッドの前後に処理を入れる



■インターセプタとは

インターセプタはSpringMVCの機能の1つで、コントローラメソッドの前後で任意の処理を実行させることができる。

インターセプタを利用するにはHandlerInterceptorAdapterを継承して、必要に応じてpreHandlepostHandleafterCompletionの3メソッドを実装する。あとはSpring設定ファイルにインターセプタを使用するための定義を記述をするだけでよい。

なおpreHandle、postHandle、afterCompletionの各メソッドは実行されるタイミングが異なる。

メソッド名実行されるタイミング
preHandleコントローラメソッドの実行前
postHandleコントローラメソッドの実行後
afterCompletionリクエスト処理が完了した後

この他「afterConcurrentHandlingStarted」というのもあるが、使用頻度が低いため説明は省く。

実際にどのようなタイミングで各メソッドが実行されるかサンプルを使って説明する。

■サンプル実行
コントローラクラスとJSPの作成

まずコントローラとJSPを作成する。それぞれでシステムアウトで文字列を出力。

SampleController.java
package mvctest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/sample")
public class SampleController {

    @RequestMapping(value="/submit1", method={RequestMethod.POST, RequestMethod.GET })
    public String submit1(Model model,
                            @RequestParam(required=false) String lastname,
                            @RequestParam(required=false) String firstname) {

        System.out.println("submit1.do");
        
        model.addAttribute("lastname", lastname);
        model.addAttribute("firstname", firstname);
        
        // sample1.jspを呼び出す
        return "sample1";
    }
}
sample1.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<% System.out.println("sample1.jsp"); %>
<!DOCTYPE>
<html>
<head>
    <meta charset="UTF-8">
    <script src="js/jquery-1.11.1.min.js"></script>
</head>
<body>

<form action="/test/sample/submit1.do" method="POST">
    <input type="submit" value="submit1">
    <input type="text" id="lastname" name="lastname" value="">
    <input type="text" id="firstname" name="firstname" value="">
    ${lastname} ${firstname}
</form>

</body>
</html>

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

インターセプタクラスの作成
SampleDao.java
package mvctest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

// HandlerInterceptorAdapterを継承
public class SampleInterceptor extends HandlerInterceptorAdapter {

    // ①コントローラメソッドの実行前に呼ばれる
    @Override
    public boolean preHandle(
                    HttpServletRequest request,
                    HttpServletResponse response,
                    Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }

    // ②コントローラメソッドの実行後に呼ばれる
    @Override
    public void postHandle(
                        HttpServletRequest request,
                        HttpServletResponse response,
                        Object handler,
                        ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    // ③リクエスト処理の完了後に呼ばれる
    @Override
    public void afterCompletion(
                        HttpServletRequest request,
                        HttpServletResponse response,
                        Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}
[説明]

インターセプタにはHandlerInterceptorAdapterを継承させる。

preHandle、postHandle、afterCompletionの3メソッドを実装し、それぞれのメソッド内で文字列を出力してみる。

各メソッドで共通の引数はHttpServletRequest、HttpServletResponse、ハンドラ(handler)と呼ばれるObjectである。このハンドラとはコントロールメソッドのこと。実態はHandlerMethodクラスで、実行されたコントローラメソッドの情報を格納している(本記事最後を参照)。

postHandleの引数ModelAndViewはビューや画面情報をまとめて保持するクラスである。コントローラメソッドの戻り値はビュー名やModelAndViewが返却できるが、ModelAndViewだった場合にここで参照できる。

afterCompletionの引数Exceptionは、例外が処理されなかった場合に、その例外クラスが引き渡される。


Spring設定ファイルの作成

インターセプタをSpring設定ファイルに登録する。

spring-context.xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.2.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd">

    <context:component-scan base-package="mvctest" />

    <mvc:annotation-driven />

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <!-- インターセプタの定義 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/*/submit*.do"/>
            <bean class="mvctest.SampleInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>

</beans>
[説明]

設定ファイルにはインターセプタのクラス名と動作対象とするパスを記述する。

パスにはコンテキストパスは含めない。例えばURLが「http://localhost/test/sample/submit1.do」でコンテキストパスが「test」の場合、以下の記述は誤りである("/test"が不要)。

誤)<mvc:mapping path="/test/*/submit*.do"/>

インターセプタは<mvc:interceptors>タグ内に複数登録できる。


全ての準備が揃ったところで実行してみる。URLへアクセスしてコントローラメソッドを実行させると、標準出力に以下が表示される。

出力結果
preHandle
submit1.do
postHandle
sample1.jsp
afterCompletion

以下の順で実行されていることがわかる。

①preHandle
②コントローラメソッド(submit1)
③posthandle
④JSP
⑤afterCompletion

フォワード処理の場合

コントローラメソッドを2つ作って、片方からもう片方へフォワードで呼び出してみる。

SampleController.java
package mvctest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/sample")
public class SampleController {

    @RequestMapping(value="/submit1", method={RequestMethod.POST, RequestMethod.GET })
    public String submit1(Model model,
                            @RequestParam(required=false) String lastname,
                            @RequestParam(required=false) String firstname) {

        System.out.println("submit1.do");
        
        model.addAttribute("lastname", lastname);
        model.addAttribute("firstname", firstname);
        
        // submit2へフォワードする
        return "forward:submit2.do";
    }

    @RequestMapping(value = "/submit2", method = { RequestMethod.POST, RequestMethod.GET })
    public String submit2(Model model) {
        System.out.println("submit2.do");
        return "sample1";
    }
}

この状態で実行すると、標準出力に以下が表示される。

出力結果
preHandle
submit1.do
postHandle
preHandle
submit2.do
postHandle
sample1.jsp
afterCompletion
afterCompletion

以下の順で実行されていることがわかる。

①preHandle
②コントローラメソッド(submit1)
③posthandle
④preHandle
⑤コントローラメソッド(submit2)
⑥posthandle
⑦JSP
⑧afterCompletion
⑨afterCompletion

preHandleとpostHandleはそれぞれのコントローラメソッドの前後で呼ばれる。なおafterCompletionも2回呼ばれるらしい。


ハンドラの出力

最後にコントローラメソッドの引数のハンドラ(Object型)を出力してみる。

System.out.println(handler.getClass());
System.out.println(handler);
出力結果
class org.springframework.web.method.HandlerMethod
public java.lang.String mvctest.SampleController.submit1(org.springframework.ui.Model,java.lang.String,java.lang.String)

コントローラメソッドの情報が出力される。引数の型はObjectだが実態はHandlerMethodクラスとなっている。

- Springの入門本 -



TOP > Spring > Interceptorでコントローラメソッドの前後に処理を入れる

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