1. 概要

このチュートリアルでは、Spring MVC HandlerInterceptorとその正しい使用方法の理解に焦点を当てます。

2. SpringMVCハンドラー

Springインターセプターがどのように機能するかを理解するために、一歩下がってHandlerMappingを見てみましょう。

HandlerMapping の目的は、ハンドラーメソッドをURLにマップすることです。 そうすれば、DispatcherServletはリクエストの処理時にそれを呼び出すことができます。

実際のところ、DispatcherServletHandlerAdapterを使用して実際にメソッドを呼び出します。

つまり、インターセプターはリクエストをインターセプトして処理します。 これらは、ロギングや認証チェックなどの繰り返しのハンドラーコードを回避するのに役立ちます。

全体的なコンテキストを理解したので、HandlerInterceptorを使用して前処理および後処理アクションを実行する方法を見てみましょう。

3. Mavenの依存関係

インターセプターを使用するには、spring-web依存関係をpom.xmlに含める必要があります。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.3.13</version>
</dependency>

4. スプリングハンドラーインターセプター

簡単に言うと、 Springインターセプターは、HandlerInterceptorAdapterクラスを拡張するか、HandlerInterceptorインターフェイスを実装するクラスです。

HandlerInterceptor には、次の3つの主要なメソッドが含まれています。

  • prehandle() –実際のハンドラーの実行前に呼び出されます
  • postHandle() –ハンドラーが実行されたの後にと呼ばれます
  • afterCompletion() –完全なリクエストが終了し、ビューが生成された後、と呼ばれます

これらの3つの方法は、あらゆる種類の前処理と後処理を行うための柔軟性を提供します。

先に進む前の簡単なメモ:理論をスキップして例に直接ジャンプするには、セクション5に進んでください。

簡単なpreHandle()の実装は次のとおりです。

@Override
public boolean preHandle(
  HttpServletRequest request,
  HttpServletResponse response, 
  Object handler) throws Exception {
    // your code
    return true;
}

メソッドがboolean値を返すことに注意してください。 リクエストをさらに処理するか( true )、処理しないか( false )をSpringに指示します。

次に、 postHandle()の実装があります。

@Override
public void postHandle(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, 
  ModelAndView modelAndView) throws Exception {
    // your code
}

インターセプターは、要求を処理した直後、ビューを生成する前にこのメソッドを呼び出します。

たとえば、このメソッドを使用して、ログインしているユーザーのアバターをモデルに追加できます。

実装する必要のある最後のメソッドは、 afterCompletion()です。

@Override
public void afterCompletion(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, Exception ex) {
    // your code
}

このメソッドを使用すると、リクエスト処理の完了後にカスタムロジックを実行できます。

さらに、複数のカスタムインターセプターを登録できることにも言及する価値があります。 これを行うには、DefaultAnnotationHandlerMappingを使用できます。

5. カスタムロガーインターセプター

この例では、Webアプリケーションへのログインに焦点を当てます。

まず、クラスはHandlerInterceptorを実装する必要があります。

public class LoggerInterceptor implements HandlerInterceptor {
    ...
}

また、インターセプターへのログインを有効にする必要があります。

private static Logger log = LoggerFactory.getLogger(LoggerInterceptor.class);

これにより、 Log4J はログを表示し、指定された出力に現在どのクラスが情報をログに記録しているかを示すことができます。

次に、カスタムインターセプターの実装に焦点を当てましょう。

5.1. preHandle()メソッド

名前が示すように、インターセプターは要求を処理する前に preHandle()を呼び出します。

デフォルトでは、このメソッドは true を返し、ハンドラーメソッドにリクエストを送信します。 ただし、 false を返すことで、実行を停止するようにSpringに指示できます。

フックを使用して、リクエストの送信元など、リクエストのパラメータに関する情報をログに記録できます。

この例では、単純なLog4Jロガーを使用してこの情報をログに記録しています。

@Override
public boolean preHandle(
  HttpServletRequest request,
  HttpServletResponse response, 
  Object handler) throws Exception {
    
    log.info("[preHandle][" + request + "]" + "[" + request.getMethod()
      + "]" + request.getRequestURI() + getParameters(request));
    
    return true;
}

ご覧のとおり、リクエストに関するいくつかの基本情報をログに記録しています。

ここでパスワードに遭遇した場合は、もちろん、それをログに記録しないようにする必要があります。 簡単なオプションは、パスワードやその他の機密タイプのデータをスターに置き換えることです。

これを行う方法の簡単な実装は次のとおりです。

private String getParameters(HttpServletRequest request) {
    StringBuffer posted = new StringBuffer();
    Enumeration<?> e = request.getParameterNames();
    if (e != null) {
        posted.append("?");
    }
    while (e.hasMoreElements()) {
        if (posted.length() > 1) {
            posted.append("&");
        }
        String curr = (String) e.nextElement();
        posted.append(curr + "=");
        if (curr.contains("password") 
          || curr.contains("pass")
          || curr.contains("pwd")) {
            posted.append("*****");
        } else {
            posted.append(request.getParameter(curr));
        }
    }
    String ip = request.getHeader("X-FORWARDED-FOR");
    String ipAddr = (ip == null) ? getRemoteAddr(request) : ip;
    if (ipAddr!=null && !ipAddr.equals("")) {
        posted.append("&_psip=" + ipAddr); 
    }
    return posted.toString();
}

最後に、HTTPリクエストの送信元IPアドレスを取得することを目指しています。

簡単な実装は次のとおりです。

private String getRemoteAddr(HttpServletRequest request) {
    String ipFromHeader = request.getHeader("X-FORWARDED-FOR");
    if (ipFromHeader != null && ipFromHeader.length() > 0) {
        log.debug("ip from proxy - X-FORWARDED-FOR : " + ipFromHeader);
        return ipFromHeader;
    }
    return request.getRemoteAddr();
}

5.2. postHandle()メソッド

インターセプターは、ハンドラーの実行後、DispatcherServletがビューをレンダリングする前にこのメソッドを呼び出します。

これを使用して、ModelAndViewに属性を追加できます。 別のユースケースは、リクエストの処理時間を計算することです。

この例では、 DispatcherServlet がビューをレンダリングする直前に、リクエストをログに記録するだけです。

@Override
public void postHandle(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, 
  ModelAndView modelAndView) throws Exception {
    
    log.info("[postHandle][" + request + "]");
}

5.3. afterCompletion()メソッド

このメソッドを使用して、ビューがレンダリングされた後に要求データと応答データを取得できます。

@Override
public void afterCompletion(
  HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 
  throws Exception {
    if (ex != null){
        ex.printStackTrace();
    }
    log.info("[afterCompletion][" + request + "][exception: " + ex + "]");
}

6. 構成

すべての要素をまとめたので、カスタムインターセプターを追加しましょう。

これを行うには、 addInterceptors()メソッドをオーバーライドする必要があります。

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoggerInterceptor());
}

XML Spring構成ファイルを編集することで、同じ構成を実現できます。

<mvc:interceptors>
    <bean id="loggerInterceptor" class="com.baeldung.web.interceptor.LoggerInterceptor"/>
</mvc:interceptors>

この構成をアクティブにすると、インターセプターがアクティブになり、アプリケーション内のすべての要求が適切にログに記録されます。

複数のSpringインターセプターが構成されている場合、 preHandle()メソッドは構成順に実行されますが、 postHandle()および afterCompletion()メソッドは次のようになります。逆の順序で呼び出されます。

バニラSpringの代わりにSpring Bootを使用している場合は、構成クラスに@EnableWebMvcのアノテーションを付ける必要がないことに注意してください。

7. 結論

この記事では、SpringMVCハンドラーインターセプターを使用してHTTPリクエストをインターセプトする方法を簡単に紹介しました。

すべての例と構成は、GitHubから入手できます。