Springboot攔截器使用及其底層源碼剖析

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"博主最近看了一下公司剛剛開發的微服務,準備入手從基本的過濾器以及攔截器開始剖析,以及在幫同學們分析一下上次的jetty過濾器源碼與本次Springboot中tomcat中過濾器的區別。正題開始,攔截器顧名思義是進行攔截請求的一系列操作。先給大家示例一下使用操作"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"1 @Configuration\n2 public class WebConfiguration implements WebMvcConfigurer {\n3 \n4 @Override\n5 public void addInterceptors(InterceptorRegistry registry) {\n6 registry.addInterceptor(new TstCfg());\n7 }\n8 }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" 1 /**\n 2 * @title: TstCfg\n 3 * @Author junyu\n 4 * 舊巷裏有一個穿着白襯衫笑起來如太陽般溫暖我的少年。\n 5 * 記憶裏有一個穿着連衣裙哭起來如孩子般討人喜的女孩。\n 6 * 他說,哪年樹彎了腰,人見了老,桃花落了白髮梢,他講的笑話她還會笑,那便是好。\n 7 * 她說,哪年國改了號,墳長了草,地府過了奈何橋,她回頭看時他還在瞧,就不算糟。\n 8 * @Date: 2020/7/29 11:53\n 9 * @Version 1.0\n10 */\n11 public class TstCfg extends HandlerInterceptorAdapter {\n12 \n13 @Override\n14 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n15 System.out.println(\"前\");\n16 return super.preHandle(request, response, handler);\n17 }\n18 \n19 @Override\n20 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {\n21 System.out.println(\"後\");\n22 }\n23 \n24 @Override\n25 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {\n26 System.out.println(\"一直會出現\");\n27 System.out.println(1/0);\n28 }\n29 }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我們可能會想到,我們的攔截器是何時裝配到攔截器數組中"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"  其實就是在springboot啓動時執行doCreateBean時,進行調用創建的org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration會在這裏放入進去所有實現了WebMvcConfigurer接口的類,一共有7個,其中就有我們自己實現了WebMvcConfigurer接口的WebConfiguration類,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/77/77276a0638d6e124c71070f5ceee6115.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們的寫的配置類WebConfiguration,繼承了WebMvcConfigurer並重寫了addInterceptors方法,所以我們的攔截器就在這時候裝配進去了。這次知道爲什麼我們寫的配置攔截器的配置示例需要繼承------WebMvcConfigurer,我們當然也可以去繼承已經實現了這個類的其他類,因爲都可以去添加攔截器,博主親試過,所以就不貼圖了!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/56/56b3d1aefd182aecd73eb96ec801971b.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"   好了,攔截器已經添加完了,那什麼時候調用我們攔截器呢?一步一步腳印來,當瀏覽器請求我們地址的 時候,分一下幾步:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 第一步:tomcat容器首先會接受到請求,這裏將會走DispatcherServlet,看到這個大家都熟悉了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e6/e65dde8ef7900ca7e2a5b4115fe5dce7.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 第二步:當然不會先走我們的攔截器了,我們的攔截器是在Springboot框架進行管理的,現在還在servlet,所以會先走到filter過濾器這一步,來貼圖:官方代碼太長,一屏截不下,前面有一個創建過濾器鏈的過程:等下次在給大家講一下jetty的過濾器鏈與tomcat的過濾器鏈的區別"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/46/46f77cfd2722babe4a1053ee4f42bd70.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 第三步:所以一旦連過濾器都沒通過的話,會直接return回去,不會再進行攔截器的調用。來貼代碼,過濾器通過後如何調用我們攔截器的"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"1 private void internalDoFilter(ServletRequest request,\n 2 ServletResponse response)\n 3 throws IOException, ServletException {\n 4 //這裏將會調用所有過濾器鏈的過濾器,不做重點講解了,看看下面攔截器的調用\n 5 // Call the next filter if there is one\n 6 if (pos < n) {\n 7 ApplicationFilterConfig filterConfig = filters[pos++];\n 8 try {\n 9 Filter filter = filterConfig.getFilter();\n10 \n11 if (request.isAsyncSupported() && \"false\".equalsIgnoreCase(\n12 filterConfig.getFilterDef().getAsyncSupported())) {\n13 request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);\n14 }\n15 if( Globals.IS_SECURITY_ENABLED ) {\n16 final ServletRequest req = request;\n17 final ServletResponse res = response;\n18 Principal principal =\n19 ((HttpServletRequest) req).getUserPrincipal();\n20 \n21 Object[] args = new Object[]{req, res, this};\n22 SecurityUtil.doAsPrivilege (\"doFilter\", filter, classType, args, principal);\n23 } else {\n24 filter.doFilter(request, response, this);\n25 }\n26 } catch (IOException | ServletException | RuntimeException e) {\n27 throw e;\n28 } catch (Throwable e) {\n29 e = ExceptionUtils.unwrapInvocationTargetException(e);\n30 ExceptionUtils.handleThrowable(e);\n31 throw new ServletException(sm.getString(\"filterChain.filter\"), e);\n32 }\n33 return;\n34 }\n35 \n36 // We fell off the end of the chain -- call the servlet instance\n37 try {\n38 if (ApplicationDispatcher.WRAP_SAME_OBJECT) {\n39 lastServicedRequest.set(request);\n40 lastServicedResponse.set(response);\n41 }\n42 \n43 if (request.isAsyncSupported() && !servletSupportsAsync) {\n44 request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,\n45 Boolean.FALSE);\n46 }\n47 // Use potentially wrapped request from this point\n48 if ((request instanceof HttpServletRequest) &&\n49 (response instanceof HttpServletResponse) &&\n50 Globals.IS_SECURITY_ENABLED ) {\n51 final ServletRequest req = request;\n52 final ServletResponse res = response;\n53 Principal principal =\n54 ((HttpServletRequest) req).getUserPrincipal();\n55 Object[] args = new Object[]{req, res};\n56 SecurityUtil.doAsPrivilege(\"service\",\n57 servlet,\n58 classTypeUsedInService,\n59 args,\n60 principal);\n61 } else {\n62 //過濾器終於完事了,現在終於開始正式調用我們的方法了,我們看看service方法做了什麼吧!\n63 servlet.service(request, response);\n64 }\n65 } catch (IOException | ServletException | RuntimeException e) {\n66 throw e;\n67 } catch (Throwable e) {\n68 e = ExceptionUtils.unwrapInvocationTargetException(e);\n69 ExceptionUtils.handleThrowable(e);\n70 throw new ServletException(sm.getString(\"filterChain.servlet\"), e);\n71 } finally {\n72 if (ApplicationDispatcher.WRAP_SAME_OBJECT) {\n73 lastServicedRequest.set(null);\n74 lastServicedResponse.set(null);\n75 }\n76 }\n77 } \n複製代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實最終它會調用到DispatcherServlet的doDispatch方法"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" 1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {\n 2 HttpServletRequest processedRequest = request;\n 3 HandlerExecutionChain mappedHandler = null;\n 4 boolean multipartRequestParsed = false;\n 5 \n 6 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);\n 7 \n 8 try {\n 9 ModelAndView mv = null;\n10 Exception dispatchException = null;\n11 \n12 try {\n13 processedRequest = checkMultipart(request);\n14 multipartRequestParsed = (processedRequest != request);\n15 \n16 // Determine handler for the current request.\n17 mappedHandler = getHandler(processedRequest);\n18 if (mappedHandler == null) {\n19 noHandlerFound(processedRequest, response);\n20 return;\n21 }\n22 \n23 // Determine handler adapter for the current request.\n24 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());\n25 \n26 // Process last-modified header, if supported by the handler.\n27 String method = request.getMethod();\n28 boolean isGet = \"GET\".equals(method);\n29 if (isGet || \"HEAD\".equals(method)) {\n30 long lastModified = ha.getLastModified(request, mappedHandler.getHandler());\n31 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {\n32 return;\n33 }\n34 }\n35 //所有攔截器開始在調用方法前攔截,如果你攔截器中返回false,則直接return不會再調用該方法!下面有源代碼\n36 if (!mappedHandler.applyPreHandle(processedRequest, response)) {\n37 return;\n38 }\n39 \n40 // Actually invoke the handler.\n41 //底層進行invoke反射,調用當前請求的方法,不用再往裏面看了\n42 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());\n43 \n44 if (asyncManager.isConcurrentHandlingStarted()) {\n45 return;\n46 }\n47 \n48 applyDefaultViewName(processedRequest, mv);\n49 //調用攔截器的postHandle,下面有源代碼\n50 mappedHandler.applyPostHandle(processedRequest, response, mv);\n51 }\n52 catch (Exception ex) {\n53 dispatchException = ex;\n54 }\n55 catch (Throwable err) {\n56 // As of 4.3, we're processing Errors thrown from handler methods as well,\n57 // making them available for @ExceptionHandler methods and other scenarios.\n58 dispatchException = new NestedServletException(\"Handler dispatch failed\", err);\n59 }\n60 //該方法中多做了一些邏輯,其實最後也調用了triggerAfterCompletion方法,最終調用攔截器方法的afterCompletion方法\n61 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);\n62 }\n63 catch (Exception ex) {\n64 //所以不管是否出現異常,攔截器方法的afterCompletion方法是一定會調用的!\n65 triggerAfterCompletion(processedRequest, response, mappedHandler, ex);\n66 }\n67 catch (Throwable err) {\n68 //所以不管是否出現異常,攔截器方法的afterCompletion方法是一定會調用的!\n69 triggerAfterCompletion(processedRequest, response, mappedHandler,\n70 new NestedServletException(\"Handler processing failed\", err));\n71 }\n72 finally {\n73 if (asyncManager.isConcurrentHandlingStarted()) {\n74 // Instead of postHandle and afterCompletion\n75 if (mappedHandler != null) {\n76 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);\n77 }\n78 }\n79 else {\n80 // Clean up any resources used by a multipart request.\n81 if (multipartRequestParsed) {\n82 cleanupMultipart(processedRequest);\n83 }\n84 }\n85 }\n86 }\n複製代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在終於開始了我們攔截器的方法了,一個一個來:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"1 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {\n 2 HandlerInterceptor[] interceptors = getInterceptors();\n 3 if (!ObjectUtils.isEmpty(interceptors)) {\n 4 for (int i = 0; i < interceptors.length; i++) {\n 5 HandlerInterceptor interceptor = interceptors[i];\n 6 //調用所有攔截器的preHandle方法\n 7 if (!interceptor.preHandle(request, response, this.handler)) {\n 8 //就算preHandle方法沒有通過,仍然會調用這個triggerAfterCompletion方法。\n 9 triggerAfterCompletion(request, response, null);\n10 return false;\n11 }\n12 this.interceptorIndex = i;\n13 }\n14 }\n15 return true;\n16 }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" 1 void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)\n 2 throws Exception {\n 3 \n 4 HandlerInterceptor[] interceptors = getInterceptors();\n 5 if (!ObjectUtils.isEmpty(interceptors)) {\n 6 for (int i = interceptors.length - 1; i >= 0; i--) {\n 7 HandlerInterceptor interceptor = interceptors[i];\n 8 //調用攔截器的postHandle方法,\n 9 interceptor.postHandle(request, response, this.handler, mv);\n10 }\n11 }\n12 }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" 1 void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)\n 2 throws Exception {\n 3 \n 4 HandlerInterceptor[] interceptors = getInterceptors();\n 5 if (!ObjectUtils.isEmpty(interceptors)) {\n 6 for (int i = this.interceptorIndex; i >= 0; i--) {\n 7 HandlerInterceptor interceptor = interceptors[i];\n 8 try {\n 9 //調用攔截器的afterCompletion方法,不管是否異常都會進行調用,但是如果該方法報異常,會被抓住。\n10 //不會影響程序正常運行,只會打印出來\n11 interceptor.afterCompletion(request, response, this.handler, ex);\n12 }\n13 catch (Throwable ex2) {\n14 logger.error(\"HandlerInterceptor.afterCompletion threw exception\", ex2);\n15 }\n16 }\n17 }\n18 }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面這個就是打印了一下,但是不會影響我們的請求響應回去:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3c/3c174ead44f54a5211b268d30a69a2e6.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 還是會正常響應回客戶端:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/04/04b1660e3af45461268afefe9e742a40.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 好了,到此攔截器的實現以及源碼分析流程到此結束,本來想給大家從Springboot的reflash方法開始解析攔截器,但是內容太多了,不僅跑題而且博主也一時半會給大家無法講解明白。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章