<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深入源碼分析SpringMVC執(zhí)行過(guò)程

          共 18910字,需瀏覽 38分鐘

           ·

          2020-08-06 10:18

          291a7472ef2117b9fbb89a7ce8ad224a.webp

          點(diǎn)擊上方“武培軒”,選擇“設(shè)為星標(biāo)”

          技術(shù)文章第一時(shí)間送達(dá)!

          本文主要講解 SpringMVC 執(zhí)行過(guò)程,并針對(duì)相關(guān)源碼進(jìn)行解析。

          首先,讓我們從 Spring MVC 的四大組件:前端控制器(DispatcherServlet)、處理器映射器(HandlerMapping)、處理器適配器(HandlerAdapter)以及視圖解析器(ViewResolver) 的角度來(lái)看一下 Spring MVC 對(duì)用戶請(qǐng)求的處理過(guò)程,過(guò)程如下圖所示:

          d1528de242cfaa5a0b76733bea37a68c.webpSpringMVC 執(zhí)行過(guò)程
          1. 用戶請(qǐng)求發(fā)送到前端控制器 DispatcherServlet
          2. 前端控制器 DispatcherServlet 接收到請(qǐng)求后,DispatcherServlet 會(huì)使用 HandlerMapping 來(lái)處理,HandlerMapping 會(huì)查找到具體進(jìn)行處理請(qǐng)求的 Handler 對(duì)象
          3. HandlerMapping 找到對(duì)應(yīng)的 Handler 之后,并不是返回一個(gè) Handler 原始對(duì)象,而是一個(gè) Handler 執(zhí)行鏈(HandlerExecutionChain),在這個(gè)執(zhí)行鏈中包括了攔截器和處理請(qǐng)求的 Handler。HandlerMapping 返回一個(gè)執(zhí)行鏈給 DispatcherServlet。
          4. DispatcherServlet 接收到執(zhí)行鏈之后,會(huì)調(diào)用 Handler 適配器去執(zhí)行 Handler
          5. HandlerAdapter執(zhí)行完成 Handler之后會(huì)得到一個(gè) ModelAndView,并返回給 DispatcherServlet。
          6. DispatcherServlet 接收到 HandlerAdapter 返回的 ModelAndView 之后,會(huì)根據(jù)其中的視圖名調(diào)用 ViewResolver。
          7. ViewResolver 根據(jù)邏輯視圖名解析成一個(gè)真正的 View 視圖,并返回給 DispatcherServlet。
          8. DispatcherServlet 接收到視圖之后,會(huì)根據(jù)上面的 ModelAndView 中的 model 來(lái)進(jìn)行視圖中數(shù)據(jù)的填充,也就是所謂的視圖渲染
          9. 渲染完成之后,DispatcherServlet 就可以將結(jié)果返回給用戶了。

          在了解了大概的執(zhí)行過(guò)程后,讓我們一起去從源碼角度去深入探索(SpringMVC 版本為 5.2.3):

          我們先創(chuàng)建一個(gè) Controller 以便進(jìn)行 debug,內(nèi)容如下:

          @Controllerpublic class SpringMvcController {    @RequestMapping("testSpringMvc")    public String testSpringMvc(Map<String, String> map) {        map.put("note", "在看轉(zhuǎn)發(fā)二連");        return "success";    }}

          然后再創(chuàng)建一個(gè) html 文件,采用 Thymeleaf 模版引擎,文件內(nèi)容如下:

          <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8">    <title>在看轉(zhuǎn)發(fā)二連title>head><body><h1 th:text="${note}">h1>body>html>

          好了,然后啟動(dòng)程序,讓我們?cè)L問(wèn) http://localhost:8080/testSpringMvc,來(lái)一步一步探索 SpringMVC 的執(zhí)行過(guò)程:

          源碼解析

          首先當(dāng)我們?cè)L問(wèn)頁(yè)面的時(shí)候,將會(huì)把請(qǐng)求發(fā)送到前端控制器 DispatcherServlet,DispatcherServlet 是一個(gè) Servlet,我們知道在 Servlet 在處理一個(gè)請(qǐng)求的時(shí)候會(huì)交給 service 方法進(jìn)行處理,這里也不例外,DispatcherServlet 繼承了 FrameworkServlet,首先進(jìn)入 FrameworkServlet 的 service 方法:

          protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {    // 請(qǐng)求方法    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());    // 若方法為 PATCH 方法或?yàn)榭談t單獨(dú)處理    if (httpMethod == HttpMethod.PATCH || httpMethod == null) {        processRequest(request, response);    }    else {// 其他的請(qǐng)求類型的方法經(jīng)由父類,也就是 HttpServlet 處理        super.service(request, response);    }}

          HttpServlet 中會(huì)根據(jù)請(qǐng)求類型的不同分別調(diào)用 doGet 或者 doPost 等方法,F(xiàn)rameworkServlet 中已經(jīng)重寫(xiě)了這些方法,在這些方法中會(huì)調(diào)用 processRequest 進(jìn)行處理,在 processRequest 中會(huì)調(diào)用 doService 方法,這個(gè) doService 方法就是在 DispatcherServlet 中實(shí)現(xiàn)的。下面就看下 DispatcherServlet 中的 doService 方法的實(shí)現(xiàn)。

          DispatcherServlet 收到請(qǐng)求


          DispatcherServlet 中的 doService方法:

          protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {    logRequest(request);    // 給 request 中的屬性做一份快照,以便能夠恢復(fù)原始屬性    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));            }        }    }    // 如果沒(méi)有配置本地化或者主題的處理器之類的,SpringMVC 會(huì)使用默認(rèn)的配置文件,即 DispatcherServlet.properties    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());    if (this.flashMapManager != null) {        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 { // 開(kāi)始真正的處理 doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // 恢復(fù)原始屬性快照 if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } }}

          接下來(lái) DispatcherServlet 開(kāi)始真正的處理,讓我們來(lái)看下 doDispatch 方法,首先會(huì)獲取當(dāng)前請(qǐng)求的 Handler 執(zhí)行鏈,然后找到合適的 HandlerAdapter,接著調(diào)用 RequestMappingHandlerAdapter 的 handle 方法,如下為 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 {            // 先檢查是不是 Multipart 類型的,比如上傳等;如果是 Multipart 類型的,則轉(zhuǎn)換為 MultipartHttpServletRequest 類型            processedRequest = checkMultipart(request);            multipartRequestParsed = (processedRequest != request);
          // 獲取當(dāng)前請(qǐng)求的 Handler 執(zhí)行鏈 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; }
          // 獲取當(dāng)前請(qǐng)求的 Handler 適配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
          // 對(duì)于 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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } }
          // 遍歷所有定義的 interceptor,執(zhí)行 preHandle 方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
          // 實(shí)際調(diào)用 Handler 的地方 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
          if (asyncManager.isConcurrentHandlingStarted()) { return; } // 處理成默認(rèn)視圖名,也就是添加前綴和后綴等 applyDefaultViewName(processedRequest, mv); // 攔截器postHandle方法進(jìn)行處理 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } // 處理最后的結(jié)果,渲染之類的都在這里 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()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }}

          查找對(duì)應(yīng)的 Handler 對(duì)象

          讓我們?nèi)ヌ剿飨率侨绾潍@取當(dāng)前請(qǐng)求的 Handler 執(zhí)行鏈,對(duì)應(yīng)著這句代碼 mappedHandler = getHandler(processedRequest);,看下 DispatcherServlet 具體的 getHandler 方法,該方法主要是遍歷所有的 handlerMappings 進(jìn)行處理,handlerMappings 是在啟動(dòng)的時(shí)候預(yù)先注冊(cè)好的,在循環(huán)中會(huì)調(diào)用 AbstractHandlerMapping 類中的 getHandler 方法來(lái)獲取 Handler 執(zhí)行鏈,若獲取的 Handler 執(zhí)行鏈不為 null,則返回當(dāng)前請(qǐng)求的 Handler 執(zhí)行鏈,DispatcherServlet 類的 getHandler 方法如下:

          protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {    if (this.handlerMappings != null) {        // 遍歷所有的 handlerMappings 進(jìn)行處理,handlerMappings 是在啟動(dòng)的時(shí)候預(yù)先注冊(cè)好的        for (HandlerMapping mapping : this.handlerMappings) {            HandlerExecutionChain handler = mapping.getHandler(request);            if (handler != null) {                return handler;            }        }    }    return null;}

          在循環(huán)中,根據(jù) mapping.getHandler(request);,繼續(xù)往下看 AbstractHandlerMapping 類中的 getHandler 方法

          public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {    // 根據(jù) request 獲取 handler    Object handler = getHandlerInternal(request);    if (handler == null) {        // 如果沒(méi)有找到就使用默認(rèn)的 handler        handler = getDefaultHandler();    }    if (handler == null) {        return null;    }    // 如果 Handler 是 String,表明是一個(gè) bean 名稱,需要尋找對(duì)應(yīng) bean    if (handler instanceof String) {        String handlerName = (String) handler;        handler = obtainApplicationContext().getBean(handlerName);    }    // 封裝 Handler 執(zhí)行鏈    return getHandlerExecutionChain(handler, request);}

          AbstractHandlerMapping 類中的 getHandler 方法中首先根據(jù) requrst 獲取 handler,主要是調(diào)用了 AbstractHandlerMethodMapping 類中的 getHandlerInternal 方法,該方法首先獲取 request 中的 url,即 /testSpringMvc,用來(lái)匹配 handler 并封裝成 HandlerMethod,然后根據(jù) handlerMethod 中的 bean 來(lái)實(shí)例化 Handler 并返回。

          protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {    // 獲取 request 中的 url,用來(lái)匹配 handler    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);    request.setAttribute(LOOKUP_PATH, lookupPath);    this.mappingRegistry.acquireReadLock();    try {        // 根據(jù)路徑尋找 Handler,并封裝成 HandlerMethod        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);        // 根據(jù) handlerMethod 中的 bean 來(lái)實(shí)例化 Handler,并添加進(jìn) HandlerMethod        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);    }    finally {        this.mappingRegistry.releaseReadLock();    }}

          接下來(lái),我們看 lookupHandlerMethod 的邏輯,主要邏輯委托給了 mappingRegistry 這個(gè)成員變量來(lái)處理:

          protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {    List matches = new ArrayList<>();    // 通過(guò) lookupPath 屬性中查找。如果找到了,就返回對(duì)應(yīng)的RequestMappingInfo    List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);    if (directPathMatches != null) {        // 如果匹配到了,檢查其他屬性是否符合要求,如請(qǐng)求方法,參數(shù),header 等        addMatchingMappings(directPathMatches, matches, request);    }    if (matches.isEmpty()) {        // 沒(méi)有直接匹配到,則遍歷所有的處理方法進(jìn)行通配符匹配        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);    }
          if (!matches.isEmpty()) { // 如果方法有多個(gè)匹配,不同的通配符等,則排序選擇出最合適的一個(gè) Comparator comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); Match bestMatch = matches.get(0); // 如果有多個(gè)匹配的,會(huì)找到第二個(gè)最合適的進(jìn)行比較 if (matches.size() > 1) { if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); } if (CorsUtils.isPreFlightRequest(request)) { return PREFLIGHT_AMBIGUOUS_MATCH; } Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); String uri = request.getRequestURI(); // 不能有相同的最優(yōu) Match throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); } } request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); // 設(shè)置 request 參數(shù)(RequestMappingHandlerMapping 對(duì)其進(jìn)行了覆寫(xiě)) handleMatch(bestMatch.mapping, lookupPath, request); // 返回匹配的 url 的處理的方法 return bestMatch.handlerMethod; } else { // 調(diào)用 RequestMappingHandlerMapping 類的 handleNoMatch 方法再匹配一次 return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); }}

          通過(guò)上面的過(guò)程,我們就獲取到了 Handler,就開(kāi)始封裝執(zhí)行鏈了,就是將我們配置的攔截器加入到執(zhí)行鏈中去,getHandlerExecutionChain 方法如下:

          protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {    // 如果當(dāng)前 Handler 不是執(zhí)行鏈類型,就使用一個(gè)新的執(zhí)行鏈實(shí)例封裝起來(lái)    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
          String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH); // 遍歷攔截器,找到跟當(dāng)前 url 對(duì)應(yīng)的,添加進(jìn)執(zhí)行鏈中去 for (HandlerInterceptor interceptor : this.adaptedInterceptors) { if (interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } return chain;}

          到此為止,我們就獲取了當(dāng)前請(qǐng)求的 Handler 執(zhí)行鏈,接下來(lái)看下是如何獲取請(qǐng)求的 Handler 適配器,主要依靠 DispatcherServlet 類的 getHandlerAdapter 方法,該方法就是遍歷所有的 HandlerAdapter,找到和當(dāng)前 Handler 匹配的就返回,在這里匹配到的為 RequestMappingHandlerAdapter。

          DispatcherServlet 類的 getHandlerAdapter 方法如下:

          protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {    if (this.handlerAdapters != null) {        // 遍歷所有的 HandlerAdapter,找到和當(dāng)前 Handler 匹配的就返回        for (HandlerAdapter adapter : this.handlerAdapters) {            if (adapter.supports(handler)) {                return adapter;            }        }    }    throw new ServletException("No adapter for handler [" + handler +            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}

          HandlerAdapter 執(zhí)行當(dāng)前的 Handler

          再獲取完當(dāng)前請(qǐng)求的 Handler 適配器后,接著進(jìn)行緩存處理,也就是對(duì) last-modified 的處理,然后調(diào)用 applyPreHandle 方法執(zhí)行攔截器的 preHandle 方法,即遍歷所有定義的 interceptor,執(zhí)行 postHandle 方法,然后就到了實(shí)際執(zhí)行 handle 的地方,doDispatch 方法中 handle 方法是執(zhí)行當(dāng)前 Handler,我們這里使用的是 RequestMappingHandlerAdapter,首先會(huì)進(jìn)入 AbstractHandlerMethodAdapter 的 handle 方法

          public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {    return handleInternal(request, response, (HandlerMethod) handler);}

          在 AbstractHandlerMethodAdapter 的 handle 方法中又調(diào)用了 RequestMappingHandlerAdapter 類的 handleInternal 方法

          protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {    ModelAndView mav;    checkRequest(request);    if (this.synchronizeOnSession) {        HttpSession session = request.getSession(false);        if (session != null) {            Object mutex = WebUtils.getSessionMutex(session);            synchronized (mutex) {                mav = invokeHandlerMethod(request, response, handlerMethod);            }        }        else {            mav = invokeHandlerMethod(request, response, handlerMethod);        }    }    else {        // 執(zhí)行方法,封裝 ModelAndView        mav = invokeHandlerMethod(request, response, handlerMethod);    }    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);        }        else {            prepareResponse(response);        }    }    return mav;}

          在執(zhí)行完 handle 方法后,然后調(diào)用 applyDefaultViewName 方法組裝默認(rèn)視圖名稱,將前綴和后綴名都加上,接著調(diào)用 applyPostHandle 方法執(zhí)行攔截器的 preHandle 方法,也就是遍歷所有定義的 interceptor,執(zhí)行postHandle 方法。

          處理最終結(jié)果以及渲染

          最終調(diào)用 DispatcherServlet 類中的 processDispatchResult 方法,此方法主要是處理最終結(jié)果的,包括異常處理、渲染頁(yè)面和發(fā)出完成通知觸發(fā)攔截器的 afterCompletion() 方法執(zhí)行等。processDispatchResult()方法代碼如下:

          private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {    boolean errorView = false;    if (exception != null) {        if (exception instanceof ModelAndViewDefiningException) {            logger.debug("ModelAndViewDefiningException encountered", exception);            mv = ((ModelAndViewDefiningException) exception).getModelAndView();        }        else {            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);            mv = processHandlerException(request, response, handler, exception);            errorView = (mv != null);        }    }    if (mv != null && !mv.wasCleared()) {        // 渲染        render(mv, request, response);        if (errorView) {            WebUtils.clearErrorRequestAttributes(request);        }    }    else {        if (logger.isTraceEnabled()) {            logger.trace("No view rendering, null ModelAndView returned.");        }    }    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {        return;    }    if (mappedHandler != null) {        mappedHandler.triggerAfterCompletion(request, response, null);    }}

          接下來(lái)讓我們看下 DispatcherServlet 類的 render 方法是如何完成渲染的,DispatcherServlet 類的 render 方法渲染過(guò)程如下:

          1. 判斷 ModelAndView 中 view 是否為 view name,沒(méi)有獲取其實(shí)例對(duì)象:如果是根據(jù) name,如果是則需要調(diào)用 resolveViewName 從視圖解析器獲取對(duì)應(yīng)的視圖(View)對(duì)象;否則 ModelAndView 中使用 getview 方法獲取 view 對(duì)象。
          2. 然后調(diào)用 View 類的 render 方法。

          DispatcherServlet 類的 render 方法如下:

          protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {    // 設(shè)置本地化    Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());    response.setLocale(locale);    View view;    String viewName = mv.getViewName();    if (viewName != null) {        // 解析視圖名,得到視圖        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);        if (view == null) {            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +                    "' in servlet with name '" + getServletName() + "'");        }    }    else {        view = mv.getView();        if (view == null) {            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +                    "View object in servlet with name '" + getServletName() + "'");        }    }
          if (logger.isTraceEnabled()) { logger.trace("Rendering view [" + view + "] "); } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // 委托給視圖進(jìn)行渲染 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; }}

          因?yàn)槲覀冇玫氖?Thymeleaf 模版引擎,所以 view.render 找到對(duì)應(yīng)的視圖 ThymeleafView 的 render 方法進(jìn)行渲染。

          public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception {    renderFragment(this.markupSelectors, model, request, response);}

          ThymeleafView 的 render 方法又調(diào)用 renderFragment 方法進(jìn)行視圖渲染,渲染完成之后,DispatcherServlet 就可以將結(jié)果返回給我們了。

          也就是 note 對(duì)應(yīng)的 value:在看轉(zhuǎn)發(fā)二連

          60fecabe3520c80b1303cb28dd92d7b5.webp總結(jié)

          通過(guò)本文的源碼分析,我相信我們都能夠清楚的認(rèn)識(shí)到 SpringMVC 執(zhí)行流程,進(jìn)一步加深對(duì) SpringMVC 的理解。

          歡迎大家點(diǎn)擊小程序留言討論(可以上傳圖片)。

          留言討論

          參考

          https://dwz.cn/DmzEGQcx

          https://dwz.cn/MSB2GJKT


          ? ? ? ?624ba162c25fc4098d35ee6692a6b41b.webp???答完這10道題,我哭了(內(nèi)含答案解析)實(shí)現(xiàn)線程的方式到底有幾種?
          你真的了解 volatile 關(guān)鍵字嗎?

          瀏覽 34
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  翔田千里 青青 久久 | 九九九九综合 | 国产精品白虎 | 91豆花网站在线视频 | 97色色综合网 |