博主最近看了一下公司剛剛開發的微服務,准備入手從基本的過濾器以及攔截器開始剖析,以及在幫同學們分析一下上次的jetty過濾器源碼與本次Springboot中tomcat中過濾器的區別。正題開始,攔截器顧名思義是進行攔截請求的一系列操作。先給大家示例一下使用操作

1 @Configuration 2 public class WebConfiguration implements WebMvcConfigurer { 3 4 @Override 5 public void addInterceptors(InterceptorRegistry registry) { 6 registry.addInterceptor(new TstCfg()); 7 } 8 }

1 /** 2 * @title: TstCfg 3 * @Author junyu 4 * 舊巷里有一個穿着白襯衫笑起來如太陽般溫暖我的少年。 5 * 記憶里有一個穿着連衣裙哭起來如孩子般討人喜的女孩。 6 * 他說,哪年樹彎了腰,人見了老,桃花落了白發梢,他講的笑話她還會笑,那便是好。 7 * 她說,哪年國改了號,墳長了草,地府過了奈何橋,她回頭看時他還在瞧,就不算糟。 8 * @Date: 2020/7/29 11:53 9 * @Version 1.0 10 */ 11 public class TstCfg extends HandlerInterceptorAdapter { 12 13 @Override 14 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 15 System.out.println("前"); 16 return super.preHandle(request, response, handler); 17 } 18 19 @Override 20 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 21 System.out.println("后"); 22 } 23 24 @Override 25 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 26 System.out.println("一直會出現"); 27 System.out.println(1/0); 28 } 29 }
首先我們可能會想到,我們的攔截器是何時裝配到攔截器數組中
其實就是在springboot啟動時執行doCreateBean時,進行調用創建的org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration會在這里放入進去所有實現了WebMvcConfigurer接口的類,一共有7個,其中就有我們自己實現了WebMvcConfigurer接口的WebConfiguration類,
我們的寫的配置類WebConfiguration,繼承了WebMvcConfigurer並重寫了addInterceptors方法,所以我們的攔截器就在這時候裝配進去了。這次知道為什么我們寫的配置攔截器的配置示例需要繼承------WebMvcConfigurer,我們當然也可以去繼承已經實現了這個類的其他類,因為都可以去添加攔截器,博主親試過,所以就不貼圖了!
好了,攔截器已經添加完了,那什么時候調用我們攔截器呢?一步一步腳印來,當瀏覽器請求我們地址的 時候,分一下幾步:
第一步:tomcat容器首先會接受到請求,這里將會走DispatcherServlet,看到這個大家都熟悉了。
第二步:當然不會先走我們的攔截器了,我們的攔截器是在Springboot框架進行管理的,現在還在servlet,所以會先走到filter過濾器這一步,來貼圖:官方代碼太長,一屏截不下,前面有一個創建過濾器鏈的過程:等下次在給大家講一下jetty的過濾器鏈與tomcat的過濾器鏈的區別
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
第三步:所以一旦連過濾器都沒通過的話,會直接return回去,不會再進行攔截器的調用。來貼代碼,過濾器通過后如何調用我們攔截器的

1 private void internalDoFilter(ServletRequest request, 2 ServletResponse response) 3 throws IOException, ServletException { 4 //這里將會調用所有過濾器鏈的過濾器,不做重點講解了,看看下面攔截器的調用 5 // Call the next filter if there is one 6 if (pos < n) { 7 ApplicationFilterConfig filterConfig = filters[pos++]; 8 try { 9 Filter filter = filterConfig.getFilter(); 10 11 if (request.isAsyncSupported() && "false".equalsIgnoreCase( 12 filterConfig.getFilterDef().getAsyncSupported())) { 13 request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE); 14 } 15 if( Globals.IS_SECURITY_ENABLED ) { 16 final ServletRequest req = request; 17 final ServletResponse res = response; 18 Principal principal = 19 ((HttpServletRequest) req).getUserPrincipal(); 20 21 Object[] args = new Object[]{req, res, this}; 22 SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal); 23 } else { 24 filter.doFilter(request, response, this); 25 } 26 } catch (IOException | ServletException | RuntimeException e) { 27 throw e; 28 } catch (Throwable e) { 29 e = ExceptionUtils.unwrapInvocationTargetException(e); 30 ExceptionUtils.handleThrowable(e); 31 throw new ServletException(sm.getString("filterChain.filter"), e); 32 } 33 return; 34 } 35 36 // We fell off the end of the chain -- call the servlet instance 37 try { 38 if (ApplicationDispatcher.WRAP_SAME_OBJECT) { 39 lastServicedRequest.set(request); 40 lastServicedResponse.set(response); 41 } 42 43 if (request.isAsyncSupported() && !servletSupportsAsync) { 44 request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, 45 Boolean.FALSE); 46 } 47 // Use potentially wrapped request from this point 48 if ((request instanceof HttpServletRequest) && 49 (response instanceof HttpServletResponse) && 50 Globals.IS_SECURITY_ENABLED ) { 51 final ServletRequest req = request; 52 final ServletResponse res = response; 53 Principal principal = 54 ((HttpServletRequest) req).getUserPrincipal(); 55 Object[] args = new Object[]{req, res}; 56 SecurityUtil.doAsPrivilege("service", 57 servlet, 58 classTypeUsedInService, 59 args, 60 principal); 61 } else { 62 //過濾器終於完事了,現在終於開始正式調用我們的方法了,我們看看service方法做了什么吧! 63 servlet.service(request, response); 64 } 65 } catch (IOException | ServletException | RuntimeException e) { 66 throw e; 67 } catch (Throwable e) { 68 e = ExceptionUtils.unwrapInvocationTargetException(e); 69 ExceptionUtils.handleThrowable(e); 70 throw new ServletException(sm.getString("filterChain.servlet"), e); 71 } finally { 72 if (ApplicationDispatcher.WRAP_SAME_OBJECT) { 73 lastServicedRequest.set(null); 74 lastServicedResponse.set(null); 75 } 76 } 77 }
其實最終它會調用到DispatcherServlet的doDispatch方法

1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 2 HttpServletRequest processedRequest = request; 3 HandlerExecutionChain mappedHandler = null; 4 boolean multipartRequestParsed = false; 5 6 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 7 8 try { 9 ModelAndView mv = null; 10 Exception dispatchException = null; 11 12 try { 13 processedRequest = checkMultipart(request); 14 multipartRequestParsed = (processedRequest != request); 15 16 // Determine handler for the current request. 17 mappedHandler = getHandler(processedRequest); 18 if (mappedHandler == null) { 19 noHandlerFound(processedRequest, response); 20 return; 21 } 22 23 // Determine handler adapter for the current request. 24 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 25 26 // Process last-modified header, if supported by the handler. 27 String method = request.getMethod(); 28 boolean isGet = "GET".equals(method); 29 if (isGet || "HEAD".equals(method)) { 30 long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); 31 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { 32 return; 33 } 34 } 35 //所有攔截器開始在調用方法前攔截,如果你攔截器中返回false,則直接return不會再調用該方法!下面有源代碼 36 if (!mappedHandler.applyPreHandle(processedRequest, response)) { 37 return; 38 } 39 40 // Actually invoke the handler. 41 //底層進行invoke反射,調用當前請求的方法,不用再往里面看了 42 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 43 44 if (asyncManager.isConcurrentHandlingStarted()) { 45 return; 46 } 47 48 applyDefaultViewName(processedRequest, mv); 49 //調用攔截器的postHandle,下面有源代碼 50 mappedHandler.applyPostHandle(processedRequest, response, mv); 51 } 52 catch (Exception ex) { 53 dispatchException = ex; 54 } 55 catch (Throwable err) { 56 // As of 4.3, we're processing Errors thrown from handler methods as well, 57 // making them available for @ExceptionHandler methods and other scenarios. 58 dispatchException = new NestedServletException("Handler dispatch failed", err); 59 } 60 //該方法中多做了一些邏輯,其實最后也調用了triggerAfterCompletion方法,最終調用攔截器方法的afterCompletion方法 61 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 62 } 63 catch (Exception ex) { 64 //所以不管是否出現異常,攔截器方法的afterCompletion方法是一定會調用的! 65 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); 66 } 67 catch (Throwable err) { 68 //所以不管是否出現異常,攔截器方法的afterCompletion方法是一定會調用的! 69 triggerAfterCompletion(processedRequest, response, mappedHandler, 70 new NestedServletException("Handler processing failed", err)); 71 } 72 finally { 73 if (asyncManager.isConcurrentHandlingStarted()) { 74 // Instead of postHandle and afterCompletion 75 if (mappedHandler != null) { 76 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); 77 } 78 } 79 else { 80 // Clean up any resources used by a multipart request. 81 if (multipartRequestParsed) { 82 cleanupMultipart(processedRequest); 83 } 84 } 85 } 86 }
現在終於開始了我們攔截器的方法了,一個一個來:

1 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { 2 HandlerInterceptor[] interceptors = getInterceptors(); 3 if (!ObjectUtils.isEmpty(interceptors)) { 4 for (int i = 0; i < interceptors.length; i++) { 5 HandlerInterceptor interceptor = interceptors[i]; 6 //調用所有攔截器的preHandle方法 7 if (!interceptor.preHandle(request, response, this.handler)) { 8 //就算preHandle方法沒有通過,仍然會調用這個triggerAfterCompletion方法。 9 triggerAfterCompletion(request, response, null); 10 return false; 11 } 12 this.interceptorIndex = i; 13 } 14 } 15 return true; 16 }

1 void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) 2 throws Exception { 3 4 HandlerInterceptor[] interceptors = getInterceptors(); 5 if (!ObjectUtils.isEmpty(interceptors)) { 6 for (int i = interceptors.length - 1; i >= 0; i--) { 7 HandlerInterceptor interceptor = interceptors[i]; 8 //調用攔截器的postHandle方法, 9 interceptor.postHandle(request, response, this.handler, mv); 10 } 11 } 12 }

1 void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) 2 throws Exception { 3 4 HandlerInterceptor[] interceptors = getInterceptors(); 5 if (!ObjectUtils.isEmpty(interceptors)) { 6 for (int i = this.interceptorIndex; i >= 0; i--) { 7 HandlerInterceptor interceptor = interceptors[i]; 8 try { 9 //調用攔截器的afterCompletion方法,不管是否異常都會進行調用,但是如果該方法報異常,會被抓住。 10 //不會影響程序正常運行,只會打印出來 11 interceptor.afterCompletion(request, response, this.handler, ex); 12 } 13 catch (Throwable ex2) { 14 logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); 15 } 16 } 17 } 18 }
下面這個就是打印了一下,但是不會影響我們的請求響應回去:
還是會正常響應回客戶端:
好了,到此攔截器的實現以及源碼分析流程到此結束,本來想給大家從Springboot的reflash方法開始解析攔截器,但是內容太多了,不僅跑題而且博主也一時半會給大家無法講解明白。