1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 18910字,需瀏覽 38分鐘

         ·

        2020-08-06 10:18

        291a7472ef2117b9fbb89a7ce8ad224a.webp

        點擊上方“武培軒”,選擇“設為星標”

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

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

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

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

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

        我們先創(chuàng)建一個 Controller 以便進行 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)建一個 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>

        好了,然后啟動程序,讓我們訪問 http://localhost:8080/testSpringMvc,來一步一步探索 SpringMVC 的執(zhí)行過程:

        源碼解析

        首先當我們訪問頁面的時候,將會把請求發(fā)送到前端控制器 DispatcherServlet,DispatcherServlet 是一個 Servlet,我們知道在 Servlet 在處理一個請求的時候會交給 service 方法進行處理,這里也不例外,DispatcherServlet 繼承了 FrameworkServlet,首先進入 FrameworkServlet 的 service 方法:

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

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

        DispatcherServlet 收到請求


        DispatcherServlet 中的 doService方法:

        protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {    logRequest(request);    // 給 request 中的屬性做一份快照,以便能夠恢復原始屬性    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));            }        }    }    // 如果沒有配置本地化或者主題的處理器之類的,SpringMVC 會使用默認的配置文件,即 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 { // 開始真正的處理 doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // 恢復原始屬性快照 if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } }}

        接下來 DispatcherServlet 開始真正的處理,讓我們來看下 doDispatch 方法,首先會獲取當前請求的 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);
        // 獲取當前請求的 Handler 執(zhí)行鏈 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; }
        // 獲取當前請求的 Handler 適配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        // 對于 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; }
        // 實際調(diào)用 Handler 的地方 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        if (asyncManager.isConcurrentHandlingStarted()) { return; } // 處理成默認視圖名,也就是添加前綴和后綴等 applyDefaultViewName(processedRequest, mv); // 攔截器postHandle方法進行處理 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); } } }}

        查找對應的 Handler 對象

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

        protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {    if (this.handlerMappings != null) {        // 遍歷所有的 handlerMappings 進行處理,handlerMappings 是在啟動的時候預先注冊好的        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) {        // 如果沒有找到就使用默認的 handler        handler = getDefaultHandler();    }    if (handler == null) {        return null;    }    // 如果 Handler 是 String,表明是一個 bean 名稱,需要尋找對應 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,用來匹配 handler 并封裝成 HandlerMethod,然后根據(jù) handlerMethod 中的 bean 來實例化 Handler 并返回。

        protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {    // 獲取 request 中的 url,用來匹配 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 來實例化 Handler,并添加進 HandlerMethod        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);    }    finally {        this.mappingRegistry.releaseReadLock();    }}

        接下來,我們看 lookupHandlerMethod 的邏輯,主要邏輯委托給了 mappingRegistry 這個成員變量來處理:

        protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {    List matches = new ArrayList<>();    // 通過 lookupPath 屬性中查找。如果找到了,就返回對應的RequestMappingInfo    List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);    if (directPathMatches != null) {        // 如果匹配到了,檢查其他屬性是否符合要求,如請求方法,參數(shù),header 等        addMatchingMappings(directPathMatches, matches, request);    }    if (matches.isEmpty()) {        // 沒有直接匹配到,則遍歷所有的處理方法進行通配符匹配        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);    }
        if (!matches.isEmpty()) { // 如果方法有多個匹配,不同的通配符等,則排序選擇出最合適的一個 Comparator comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); Match bestMatch = matches.get(0); // 如果有多個匹配的,會找到第二個最合適的進行比較 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); // 設置 request 參數(shù)(RequestMappingHandlerMapping 對其進行了覆寫) handleMatch(bestMatch.mapping, lookupPath, request); // 返回匹配的 url 的處理的方法 return bestMatch.handlerMethod; } else { // 調(diào)用 RequestMappingHandlerMapping 類的 handleNoMatch 方法再匹配一次 return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); }}

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

        protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {    // 如果當前 Handler 不是執(zhí)行鏈類型,就使用一個新的執(zhí)行鏈實例封裝起來    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH); // 遍歷攔截器,找到跟當前 url 對應的,添加進執(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;}

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

        DispatcherServlet 類的 getHandlerAdapter 方法如下:

        protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {    if (this.handlerAdapters != null) {        // 遍歷所有的 HandlerAdapter,找到和當前 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í)行當前的 Handler

        再獲取完當前請求的 Handler 適配器后,接著進行緩存處理,也就是對 last-modified 的處理,然后調(diào)用 applyPreHandle 方法執(zhí)行攔截器的 preHandle 方法,即遍歷所有定義的 interceptor,執(zhí)行 postHandle 方法,然后就到了實際執(zhí)行 handle 的地方,doDispatch 方法中 handle 方法是執(zhí)行當前 Handler,我們這里使用的是 RequestMappingHandlerAdapter,首先會進入 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 方法組裝默認視圖名稱,將前綴和后綴名都加上,接著調(diào)用 applyPostHandle 方法執(zhí)行攔截器的 preHandle 方法,也就是遍歷所有定義的 interceptor,執(zhí)行postHandle 方法。

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

        最終調(diào)用 DispatcherServlet 類中的 processDispatchResult 方法,此方法主要是處理最終結(jié)果的,包括異常處理、渲染頁面和發(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);    }}

        接下來讓我們看下 DispatcherServlet 類的 render 方法是如何完成渲染的,DispatcherServlet 類的 render 方法渲染過程如下:

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

        DispatcherServlet 類的 render 方法如下:

        protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {    // 設置本地化    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()); } // 委托給視圖進行渲染 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; }}

        因為我們用的是 Thymeleaf 模版引擎,所以 view.render 找到對應的視圖 ThymeleafView 的 render 方法進行渲染。

        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 方法進行視圖渲染,渲染完成之后,DispatcherServlet 就可以將結(jié)果返回給我們了。

        也就是 note 對應的 value:在看轉(zhuǎn)發(fā)二連。

        60fecabe3520c80b1303cb28dd92d7b5.webp總結(jié)

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

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

        留言討論

        參考

        https://dwz.cn/DmzEGQcx

        https://dwz.cn/MSB2GJKT


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

        瀏覽 34
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            越南一级淫片在线观看 | 日本卖婬高清免费播放 | 东京热一区二区三区精品无码 | 女主被各种怪物啪h | 午夜寻花 | 吴梦梦做爱视频 | 亚洲男男在线 | 婷婷www | 国产美女被爆 精品网站 | 国产美女一级真毛片酒店 |