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メソッドを実行各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