daisuzz.log

【Spring】HandlerInterceptorの各メソッドが呼ばれる流れを追ってみた

HandlerInterceptorが呼ばれる流れを追ってみる。 ソースコードのバージョンはSpring Boot 2.3.0.RELEASE

HandlerInterceptorは以下のようにpreHandle, postHandle, afterComplitionを実装することで、Controllerのメソッド(ハンドラ)の前後に任意の処理を実行させることができる。

public class FooInterceptor 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`");
    }
}

今回は、preHandle, postHandle, afterCompletionがいつどのように実行されているのかをコードを読んで追っていく。

まずは各メソッドを呼び出している箇所を読んでみる。 各メソッドはそれぞれ、HandlerExecutionChainクラスのapplyPreHandle, applyPostHandle, triggerAfterCompletionというメソッド内で呼ばれている。

public class HandlerExecutionChain {

    /* 中略 */

    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;
    }
    
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable 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);
            } 
        } 
    }

    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable 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);
                }
            }
        }
    }
    
    /* 中略 */
}

applyPreHandleでは、定義されているinterceptorのpreHandleメソッドを順に呼び出していき、 全てがtrueを返す場合(interceptorのpreHandleを実行して問題がなかった場合)は後続の処理を進めている。 preHandleを実行した結果、falseを返すinterceptorがあった場合は、 afterCompletionを実行するメソッドtriggerAfterCompletionを呼び出した後、リクエストを失敗させる。

applyPostHandleでは、定義されているinterceptorのpostHandleメソッドを順に呼び出していき、その後後続の処理を進めていく。

triggerAfterCompletionでは、定義されているinterceptorのafterCompletionメソッドを順に呼び出していく。 このとき実行されるinterceptorはpreHandleでtrueを返したinterceptorだけになるので注意が必要。

次にapplyPreHandle, applyPostHandle, triggerAfterCompletionメソッドを呼び出している箇所を読んでいく。 applyPreHandle, applyPostHandle, triggerAfterCompletionメソッドはDispatcherServletのdoDispatchメソッドで呼ばれている。 このメソッドは、リクエストに対応するハンドラ(Controllerのメソッド)を探し、処理の委譲を行う。

public class DispatcherServlet extends FrameworkServlet {

    /* 中略 */

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

        /* 中略 */

        mappedHandler = getHandler(processedRequest);

        /* 中略 */

        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        /* 中略 */

        mappedHandler.applyPostHandle(processedRequest, response, mv);

        /* 中略 */
        
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        
        /* 中略 */
    }

    /* 中略 */

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
        @Nullable Exception exception) throws Exception {
        
        /* 中略 */

        if (mv != null && !mv.wasCleared()) {
            render(mv, request, response);
            if (errorView) { 
                WebUtils.clearErrorRequestAttributes(request); 
            } 
        }
        
        /* 中略 */

        if (mappedHandler != null) { 
            mappedHandler.triggerAfterCompletion(request, response, null); 
        } 
    }
}

dispatcherServlet.doDispatchメソッドの大まかな処理の流れは、以下のような流れになっている。

  • getHandlerメソッドでHandlerExecutionChainを取得

  • 各interceptorのpreHandleメソッドを実行

  • リクエストに対応するハンドラを実行

  • 各interceptorのpostHandleメソッドを実行

  • MVCの機能を使っている場合はレンダリング処理を実行

  • 各interceptorのafterCompletionを実行

コードを読んでみて、afterCompletionレンダリング処理の後に実行される、とかDispatcherServlet.doDispatchメソッドを読めばHandlerInterceptorの実行処理の流れがわかる、とかHandlerExecutionChainがinterceptorのリストを持っている、など色々学びがあった。

参考資料

spring-framework/HandlerInterceptor.java at master · spring-projects/spring-framework · GitHub

spring-framework/HandlerExecutionChain.java at master · spring-projects/spring-framework · GitHub

spring-framework/DispatcherServlet.java at master · spring-projects/spring-framework · GitHub