mvc,springmvc源碼閱讀3--dispatcherServlet reqeust的執行流程

 2023-10-09 阅读 29 评论 0

摘要:一、前言 太多的時候總是會遇到群里面問,報404怎么肥事呀,前臺的參數怎么后臺收不到呀……,上次就在群里面遇到過,圍繞這一個點:input的name值是不是錯了呀,人家妹子都截了好幾次圖說沒有問題,然而這個話題還是繼續了30多分

一、前言

太多的時候總是會遇到群里面問,報404怎么肥事呀,前臺的參數怎么后臺收不到呀……,上次就在群里面遇到過,圍繞這一個點:input的name值是不是錯了呀,人家妹子都截了好幾次圖說沒有問題,然而這個話題還是繼續了30多分鐘,看不下去了就私聊 妹子說:debug FrameworkServlet.java中的processRequest方法,一步步的確定問題定位問題,該討論卒!!

框架的出現大大的提升了開發效率,以及提升項目的規范性,但是也提升了學習的成本,以及遇到問題解決問題的成本,運氣好的我們在網上可以找到解決的方案,但是假若找不到我們也要學會去定位問題,逐步的剖析問題而不是手足無措,群里面能解決的問題都比較有限,畢竟老鳥一般都在忙項目和家庭,所以還是需要依靠自己。

同時也說一下我寫博客的大致的思路

  • 要么就是以應用場景來寫起,例如:不為人知的springboot的技巧就是完全以應用場景來驅動的
  • 源碼解析,一般通常都會給出流程圖出來的,就算不想看下面的具體的解析,也可以依據這個流程圖來自己看代碼(很多時候框架考慮多種情況,具體調用的實現類不一樣,要具體分析(不要噴我喲))

二 、把圖呈上來

image.png

三、具體的核心代碼分析:

在這里值分享核心的代碼點和感興趣的代碼點

3.1 FrameworkServlet

3.1.1 service()

	@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());// 這個地方是防止一些特殊的請求,service中不能覆蓋到。所以做的一個判斷if (httpMethod == HttpMethod.PATCH || httpMethod == null) {processRequest(request, response);} else {/***	這個地方雖然是調用了父類的service()方法,但是最終調用的還是 doPost|doDelete …… 等方法*  最核心的方法還是:processRequest*/super.service(request, response);}}

mvc、這個其實沒有什么好說的,這個是j2ee的規范來的,service來處理業務請求,在super.service()方法中其實最后調用的還是processRequest(),這個是個核心的方法

3.1.1 processRequest()

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 這個地方是記錄請求的開始的時間,做計時用的,下面有一個事件發布,其實我們監聽那個事件就可以知道每次的請求的耗時long startTime = System.currentTimeMillis();Throwable failureCause = null;// 這逼完全是為了國際化的LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();LocaleContext localeContext = buildLocaleContext(request);/*** 這個地方就是,spring對于reqeust的一些參數的封裝的地方* 很多的時候,都會新手去問為啥我前臺傳的參數,后臺收不到,我的name值沒有寫錯呀,遇到這種問題我們要學會去分析* - 1、debut processRequest 方法,查看參數有沒有到這個地方,如果有的同時還需要檢查格式是否正確* - 2、上一步沒有問題的情況下,還有一中情況,就是java(spring)對于json的請求參數的支持需要加注解的* 	  - 2.1、有很多時候我們的參數是實體,那么我們還要去找對應的解析實體的部分* 通過上面的一些解析,就能大概的查詢到問題出在什么地方了。*//** 是個自定義標簽,大家可以百度一下如何自定標簽* iftk-doubt  這個地方沒有明白下面的語句的意義是什么,當然也有可能我的理解有誤。* 但是肯定不想網上所說的那樣,是取的上一次的 屬性集,* 因為這個是依據本次線程的來獲取的,另外這里什么時候設置進去的是tomcat的請求就已經設置好了* RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();** 初步估計這個可能涉及到獲取相關的filter等信息,只是估計 待驗證*/RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();// 這個沒有什么好說的,直接封裝當前的 請求屬性集ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);// 這個分支我們先不管,從名字中來看應該是做異步請求的部分WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());/*** 這里才是把request同當前的線程綁定起來的實現* 要注意一下這里的知識點:ThreadLocal 只能在同一個線程的生命周期中共享變量* 如果在父子線程中要想共享變量的話,只能通過InheritableThreadLocal才能夠實現** 這里就是我們上次所說的springboot你不知道的點的實例三的實現代碼,當然這個部分springmvc也是可以用的*/initContextHolders(request, localeContext, requestAttributes);try {// 調用子類來處理請求doService(request, response);} catch (ServletException | IOException ex) {failureCause = ex;throw ex;} catch (Throwable ex) {failureCause = ex;throw new NestedServletException("Request processing failed", ex);} finally {// 后續的做一些釋放處理,因為這里可能涉及到,之前設置的filter等相關的信息resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}if (logger.isDebugEnabled()) {if (failureCause != null) {this.logger.debug("Could not complete request", failureCause);} else {if (asyncManager.isConcurrentHandlingStarted()) {logger.debug("Leaving response open for concurrent processing");} else {this.logger.debug("Successfully completed request");}}}/***  不管如何這里都把事件發布出去。*  這里我們可以通過  implements ApplicationListener<ServletRequestHandledEvent> 實現onApplicationEvent 方法*  	來獲取相關的信息*  url=[/user/register];*    	client=[0:0:0:0:0:0:0:1];*    	method=[GET];*    	servlet=[dispatcherServlet];*    	session=[null];*    	user=[null];*    	time=[8ms];*    	status=[OK]**/publishRequestHandledEvent(request, response, startTime, failureCause);}}

3.2 DispatcherServlet 這個是 SpringMvc 的核心的調度類

3.2.1 doService() 這個是 j2ee 的規范來的,servlet生命周期中處理請求的方法

方法的主要作用就是對 request 進一步的增強,設置一些屬性。并沒有太多精華的地方,著重的我們要看doDispatch(request, response);這個實際的調度的功能

/*** Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}* for the actual dispatching.* 這里翻譯的大致意思是:將特定于DispatcherServlet的請求參數暴漏出來并委托給實際的調度者。* 大概的意思是:通過判斷之前封裝的特殊的標記(一些springmvc封裝的參數)。來找不同的處理者來進行處理*/@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {if (logger.isDebugEnabled()) {String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");}/*** isIncludeRequest* public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";* 這里的是為<jsp:incluede page="xxx.jsp"/>  這中指令做的一個需求,這里可以看到j2ee的架構還是比較大的,同時歷史的包袱* 是很重的,在這里我們不做過多的講解*/Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap<>();Enumeration<?> attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {String attrName = (String) attrNames.nextElement();if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// 提過框架對象處理程序和視圖對象request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());// 還是國際化的部分request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);// 這個是springmvc 支持的主題request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());// 對于重定向的場景,這里把數據緩存起來,保證在重定向后還能獲取一些數據if (this.flashMapManager != null) {// retrieveAndUpdate 這個地方是借助于session來緩存數據的FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);}try {// 這里做實際的調度doDispatch(request, response);} finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Restore the original attribute snapshot, in case of an include.if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}}

3.3 doDispatch 方法

這個方法是整個 request 的執行的調度的執行官,接受請求 request 然后借助九大組件的組合處理返回 response,所以這個作為請求的核心的入口,我們依據這條線來查看后續的所有的處理流程的源碼

/** 大體的意思就是,這里將調用按照servlet映射的順序來獲取相應的handler來進行實際的處理* Process the actual dispatching to the handler. 簡單翻譯:調度適配的handler來程序* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters* to find the first that supports the handler class.* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers* themselves to decide which methods are acceptable.** @param request  current HTTP request* @param response current HTTP response* @throws Exception in case of any kind of processing failure*/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.// 通過對于request的種類來獲取相應的handler, 看下這個地方是如何獲取 handler 的// @iftk-mark 這個地方需要著重的看一下mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}/** 其實這個地方就是找尋controller的處理方法來的*  找到合適的處理器來處理請求*  ha ==> public java.lang.String top.fkxuexi.business.controller.MultiYmlTestController.ymlTest()*/HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.// 這個是對于header中是否包含last-modified的處理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;}}// 這里就比較爽了,這個地方是 spring 的 interceptor 調用的地方if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}/***真正的調用適配起來來進行處理 , 調用controller的方法來進行處理 這個通常我們看* @see org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle*/mv = ha.handle(processedRequest, response, mappedHandler.getHandler());mv = mv;if (asyncManager.isConcurrentHandlingStarted()) {return;}// 解析視圖applyDefaultViewName(processedRequest, mv);/*** HandlerInterceptor 執行HandlerInterceptor鏈,并執行post處理*/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);}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 afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

3.3.1、上面的代碼中,我們需要注意的有意義的代碼

  • HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    這個 handler 是何時注入進去的
  • !mappedHandler.applyPreHandle(processedRequest, response)
    這個地方的調用 interceptor 來做攔截處理,什么時候注入的,執行順序如何(這個比較簡單在spirng中,直接按照xml的順序解析即可,在springboot 當中直接按照addInterceptor的順序執行即可)
  • mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    這個地方就是實際的執行流程的處理的地方了,在這里我們將看到 springmvc 對于參數上的處理,以及如何通過反射調用我們 controller 的類的方法

3.3.2 上面沒有提到的,但是需要思考的

  • springmvc 是借助于 url 來定位我們的 controller 的類的方法的,但是我們這篇包括之前的博文都沒有講到,是如何定位的,這針對我們的 404 除了參數引起的外還有可能是 url 的解析問題引起的,所以這個看是得看看的,因為這個可能涉及到 spring 的ioc 的初始化過程,這個過程是spring的核心也是相當復雜的,所以我們以先撿軟柿子捏的原則和態度,先搞定其他的,后面我們帶這個問題在繼續探討
  • 上面也有提到過,是如何借助于適配器模式,來為我們挑選 handler 的,這個上述的代碼中也沒有講到,這也是我們以后需要帶著這些問題去繼續閱讀源碼的

四、借助于idae自定義標簽:iftk-doubt

在眾多的框架都興衰的過程中,spring 一直一枝獨秀經久不衰,由于 spring 的架構體系過于的龐大,很多類經常都是 2000 多行左右的,眾多的代碼眾多的類,如何快速的標記我們感興趣的點,同時標記我們有疑惑的點,我們需要善用我們的開發工具,todo功能,todo 是編輯器為我們自帶的,但是我們發現 spring 的源碼中也有很多的 todo ,為了區別是 spring 自帶的還是我們標注的,所以我們需要借助于自定義標簽來做以區分。下面是如何自定義標簽的方法:直接打開idea 的setting 在輸出框中輸出 todo 會出現下面的選項卡,直接輸入自己自定的標簽即可

image.png
image.png
image.png

  • 疑惑的點:這個代碼沒有搞懂含義,
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

這些地方我都做有標記即上面大的那些標簽,idea 的這個地方是做的相當的贊的。
image.png

java spring mvc、上面所說道的參數的解析的部分以及如何把請求參數封裝成我們方法中的model,這些都在spring的九大組件之一的 HandlerAdapter 的流程的解析當中,這里我們僅僅是分析一下流程,下面我們則具體到流程線上的某些點來具體的探討,來探討 spring 是如何的優雅的將這些處理器注入到處理流程當中的,來探討 spring 如何優雅的使用設計模式的,當然這些或許不再下次的博文的討論范疇當中,但是如果看 spring 的源碼,這些還是必須要搞懂的畢竟精華就在這個地方。

博客首發地址csdn:https://blog.csdn.net/weixin_42849915
簡書地址:https://www.jianshu.com/u/4b48be4cf59f
希望結識更多的樂于分享的伙伴一起前行

轉載于:https://www.cnblogs.com/fkxuexi/p/10674035.html

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/134285.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息