LoginSignup
16
14

More than 5 years have passed since last update.

SpringのHandlerInterceptorの実行順序

Posted at

Interceptorの実行順序ってどうなってるんだろうと思い、ソースコードを読んだ内容をまとめます。

Interceptorの基本

HanlderInterceptorには3つのメソッドがあります。
それらが実行されるタイミングは以下の通りです。

メソッド名 実行タイミング
preHandle handlerが実行される前に呼び出されます。
postHandle handlerの実行が正常に完了した後に呼び出されます。handlerで処理されない例外が発生した場合は実行されません。
afterCompletion Viewによるrender処理が完了した後に呼び出されます。handlerやViewのrender処理で例外が発生していても実行されます。

Interceptorが複数あるとどうなるのか

ここからが本題になってきます。
この辺の処理の流れを理解するには、DispatcherServlet.doDispatchメソッドを追いかけるといいです。

まず、HandlerExecutionChain.applyPreHandleメソッドでpreHandleを実行していることがわかります。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
    for (int i = 0; i < interceptors.length; i++) {
      HandlerInterceptor interceptor = interceptors[i];
      if (!interceptor.preHandle(request, response, this.handler)) {
        triggerAfterCompletion(request, response, null);
        return false;
      }
      this.interceptorIndex = i;
    }
  }
  return true;
}

上記を見てわかる通り、for文でループして実行しているだけですので、配列に格納されている順序で実行されることがわかります。
Interceptorはどういう順序で格納されるのかがわかれば、実行順序もわかります。

が、ここからはなかなか処理を追うのが困難でした。

XMLで<mvc:interceptors>タグを用いている場合は、InterceptorsBeanDefinitionParserによって、Bean定義の処理が行われます。
ここでは、XMLを上から順に処理しているので、基本的にはInterceptorを定義した順に実行するのであろうと思われます。

preHandle

先ほどの通り、Bean定義された順に実行されます。

postHandle

HandlerExecutionChain.applyPostHandleメソッドから実行されます。

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
    for (int i = interceptors.length - 1; i >= 0; i--) {
      HandlerInterceptor interceptor = interceptors[i];
      interceptor.postHandle(request, response, this.handler, mv);
    }
  }
}

forループが逆順になっています。そのため、preHandleとは逆順で実行されます。

afterCompletion

HandlerExecutionChain.triggerAfterCompletionメソッドから実行されます。

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
  throws Exception {

  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
    for (int i = this.interceptorIndex; i >= 0; i--) {
      HandlerInterceptor interceptor = interceptors[i];
      try {
        interceptor.afterCompletion(request, response, this.handler, ex);
      }
      catch (Throwable ex2) {
        logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
      }
    }
  }
}

これもforループが逆順になっています。そのため、preHandleとは逆順で実行されます。

preHandleがfalseを返却したらどうなるのか

preHandleはbooleanを返却します。falseを返却した場合は、handlerの実行が行われません。
では、Interceptorが複数ある場合はどうなるのでしょうか。

HandlerExecutionChain.applyPreHandleメソッドを見ればわかりますね。

(再掲)

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
    for (int i = 0; i < interceptors.length; i++) {
      HandlerInterceptor interceptor = interceptors[i];
      if (!interceptor.preHandle(request, response, this.handler)) {
        triggerAfterCompletion(request, response, null);
        return false;
      }
      this.interceptorIndex = i;
    }
  }
  return true;
}

preHandleがfalseを返却した段階で、triggerAfterCompletionに処理が移っています。
また、preHandleがtrueを返却すると、interceptorIndexが更新されています。

triggerAfterCompletionでは、interceptorIndexを利用して、preHandleが正常に終了しているInterceptorのafterCompletionメソッドを呼び出しています。

16
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
14