一、拦截器的作用
将通用的代码抽取出来,达到复用的效果。比如可以用来做日志记录、登录判断、权限校验等等
二、如何实现自定义拦截器
1)创建自定义拦截器类并实现HandlerInterceptor类
/** * @author zhangboqing * @date 2019-07-28 */ public class MyInterceptor implements HandlerInterceptor { /** * 执行Controller方法之前,调用 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //返回false,请求将被拦截。放回true代表放行 return false; } /** * 执行Controller方法之后,响应给前端之前,调用 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * 响应给前端之后,调用 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
2)将我们自已的拦截器注册到注册器中
/** * @author zhangboqing * @date 2019-07-28 * * MVC配置类 */ @Configuration public class MyWebMvcConfigurer implements WebMvcConfigurer { /** 创建自定义拦截器实例 */ @Bean MyInterceptor myInterceptor() { return new MyInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { //注册自定义拦截器 registry.addInterceptor(myInterceptor()) //拦截所有请求 .addPathPatterns("/*") //指定需要过滤的请求地址 // .excludePathPatterns() ; } }
三、请求日志记录拦截器实现
import com.alibaba.fastjson.JSON; import com.talkilla.talkillalexile.common.utils.JodaTimeUtils; import com.talkilla.talkillalexile.common.utils.RandomCodeUtils; import com.talkilla.talkillalexile.config.filter.HttpHelperUtils; import com.talkilla.talkillalexile.config.interceptor.model.PostRequestLogInfoModel; import com.talkilla.talkillalexile.config.interceptor.model.PreRequestLogInfoModel; import lombok.extern.slf4j.Slf4j; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; /** * @author zhangboqing * @date 2018/10/13 * <p> * 日志打印拦截 */ @Slf4j public class LoggingInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //生成这次请求的唯一标识 String requestUUID = RandomCodeUtils.getUUID(); long logStartTime = System.currentTimeMillis(); //记录开始时间 request.setAttribute("logStartTime", logStartTime); request.setAttribute("requestUUID",requestUUID); //请求日志记录 preRequestLoggin(request,requestUUID); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { long logEndTime = System.currentTimeMillis(); long logStartTime = (Long)request.getAttribute("logStartTime"); String requestUUID = (String)request.getAttribute("requestUUID"); //记录整个请求的执行时间 loggingHandleTime(requestUUID,logStartTime,logEndTime); } private void loggingHandleTime(String requestUUID, long logStartTime, long logEndTime) { String logInfo = getLoggingHandleTime(requestUUID,logStartTime,logEndTime); log.info("[请求拦截日志信息]:{}", logInfo); } private String getLoggingHandleTime(String requestUUID, long logStartTime, long logEndTime) { PostRequestLogInfoModel build = PostRequestLogInfoModel.builder() .requestUUID(requestUUID) .requestTime(JodaTimeUtils.timestampToString(logStartTime / 1000, JodaTimeUtils.DateFormat.DATETIME_FORMAT)) .responseTime(JodaTimeUtils.timestampToString(logEndTime / 1000, JodaTimeUtils.DateFormat.DATETIME_FORMAT)) .handleTime((logEndTime - logStartTime) + "ms").build(); return JSON.toJSONString(build); } /** * 请求日志记录 * * @param request */ private void preRequestLoggin(HttpServletRequest request,String requestUUID) { //获取相关参数 //请求地址 String requestURI = request.getRequestURI(); //请求方法 String method = request.getMethod(); //请求参数 Map<String, String[]> parameterMap = request.getParameterMap(); String bodyString = ""; try { bodyString = HttpHelperUtils.getBodyString(request); } catch (IOException e) { e.printStackTrace(); } String reqestLogInfo = getRequestLogInfo(requestURI, method, parameterMap, bodyString,requestUUID); log.info("[请求拦截日志信息]:{}", reqestLogInfo); } private String getRequestLogInfo(String requestURI, String method, Map<String, String[]> getParameterMap, String postBodyString,String requestUUID) { PreRequestLogInfoModel build = PreRequestLogInfoModel.builder() .requestUUID(requestUUID) .requestURI(requestURI) .method(method) .getParameter(getParameterMap) .postParameter(postBodyString).build(); return JSON.toJSONString(build); } }
四、从源码角度去理解拦截器三个方法的执行时机
将代码定位到Spring MVC核心处理类DispatcherServlet的doDispatcher()方法,从标记的1,2,3,4,5可以很清楚的看出下面几点启示
1.启示一:拦截器的preHandle方法是在执行Controller方法之前被调用的
2.启示二:拦截器的postHandle方法是在执行Controller方法之后被调用的,但是再处理响应结果之前
3.启示三:拦截器的afterCompletion方法是在处理响应结果之后执行的,也就是说,在调用afterCompletion方法的时候,响应结果已经返回给前端了,该方法的任何处理都不会影响响应结果
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); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 1.执行拦截器preHandle()方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 2.实际调用处理程序(Controller的方法) mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv);
// 3.执行拦截器postHandle()方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); }
// 4.处理响应结果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { // 5.执行拦截器afterCompletion()方法 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); } } } }