近期在面试的时候问到了 spring mvc 的 HandlerInterceptor,用过但是没深入,记录一下。
以下代码为 spring boot 2.7.15 中自带的 spring 5.3.29
如下为 DispatcherServlet 中的 doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // 确定当前请求的处理器 HandlerExecutionChain mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // 确定当前请求的处理适配器 HandlerAdapter,对应实现类为 RequestMappingHandlerAdapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 针对定义的 HandlerInterceptor 重写 preHandle() 处理对应的逻辑 // 执行时机:HandlerExecutionChain 对象中在进入 handler 之前按序处理,如果任何一个 preHandle() 返回 false,则终止处理 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 实际调用 handler mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); // 针对定义的 HandlerInterceptor 重写 postHandle() 处理对应的逻辑,如果有多个,逆序处理 // 执行时机:HandlerExecutionChain 对象中在进入 handler 之后,渲染视图之前进行处理 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } // 针对定义的 HandlerInterceptor 重写 afterCompletion() 处理对应的逻辑,如果有多个,将执行完 preHandle() 后最后一个返回值为 true 的下标进行逆序处理 // 执行时机:HandlerExecutionChain 对象中在进入 handler 之后,渲染视图接触后进行处理 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
其中通过 HandlerExecutionChain、HttpServletRequest、HttpServletResponse、HandlerAdapter、HandlerExecutionChain 来进行请求处理。
处理请求的是 HandlerAdapter 的实现类 RequestMappingHandlerAdapter。这是主要的请求流程。
HandlerExecutionChain 负责处理 HandlerInterceptor 的处理过程,作为辅助的功能。没有这个请求照样进行,但是没有 HandlerAdapter 就不行了。
HandlerInterceptor 是一个接口,其中有三个方法
package org.springframework.web.servlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.lang.Nullable; import org.springframework.web.method.HandlerMethod; public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
从 DispatcherServlet#doDispatch() 的代码逻辑可知,HandlerInterceptor 的执行顺序为
preHandle() ↓ postHandle() ↓ afterCompletion()
preHandle()
针对定义的 HandlerInterceptor 重写 preHandle() 处理对应的逻辑。
执行时机:HandlerExecutionChain 对象中在进入 handler 之前按序处理,如果任何一个 preHandle() 返回 false,则终止处理。
postHandle()
针对定义的 HandlerInterceptor 重写 postHandle() 处理对应的逻辑,如果有多个,逆序处理。
执行时机:HandlerExecutionChain 对象中在进入 handler 之后,渲染视图之前进行处理。
即 postHandle() 要执行,则 preHandle() 的返回值必须为 true。
afterCompletion()
针对定义的 HandlerInterceptor 重写 afterCompletion() 处理对应的逻辑,如果有多个,将执行完 preHandle() 后最后一个返回值为 true 的下标进行逆序处理。
执行时机:HandlerExecutionChain 对象中在进入 handler 之后,渲染视图接触后进行处理。
HandlerInterceptor 的三个方法具体执行逻辑在 HandlerExecutionChain 中。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { for (int i = 0; i < this.interceptorList.size(); i++) { HandlerInterceptor interceptor = this.interceptorList.get(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 { for (int i = this.interceptorList.size() - 1; i >= 0; i--) { HandlerInterceptor interceptor = this.interceptorList.get(i); interceptor.postHandle(request, response, this.handler, mv); } } void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) { for (int i = this.interceptorIndex; i >= 0; i--) { HandlerInterceptor interceptor = this.interceptorList.get(i); try { interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); } } }
通过 postHandle() 和 afterCompletion() 的处理顺序让我想到了责任链模式。
看到这个想到了 servlet 的 Filter,拦截器能做的 Filter 也能做,并且不局限于 spring mvc,拦截器是 spring mvc 专有的功能,但是 servlet 是一个规范,只要一个服务对外暴露服务,使用了 servlet 容器处理这些服务(没有使用响应式处理)就可以。
Filter 的优先级比拦截器要高。
package javax.servlet; import java.io.IOException; public interface Filter { default void init(FilterConfig filterConfig) throws ServletException { } void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; default void destroy() { } }
使用场景
权限校验
如果用户未登录,则在 preHandle() 方法处理后返回 false,进行登录处理。
性能监控
统计请求的耗时,在 preHandle() 和 afterCompletion() 中添加统计相关的处理逻辑。
日志记录
在 preHandle() 记录请求方式、请求参数等。
其中监控和日志可以通过 aop 来实现。
之前自己写的文章
https://blog.csdn.net/zlpzlpzyd/article/details/133499304
参考链接
https://www.zhihu.com/question/39510340/answer/3054411211
https://blog.csdn.net/cristianoxm/article/details/123215237
https://www.cnblogs.com/seanstudy/p/15848259.html