接着上一篇。繼續來看springMVC中最和咱們開發中接近的一部份內容:ios
DispatcherServlet的邏輯處理web
做者寫到在DispatcherServlet類中存在doGet、doPost之類的方法,可是在我查看的這個spring版本中,並非在這個類中,而是在其父類FrameworkServlet中,從FrameworkServlet開始看起!spring
org.springframework.web.servlet.FrameworkServlet類中:緩存
1 @Override 2 protected final void doGet(HttpServletRequest request, HttpServletResponse response) 3 throws ServletException, IOException { 4 5 processRequest(request, response); 6 } 7 8 @Override 9 protected final void doPost(HttpServletRequest request, HttpServletResponse response) 10 throws ServletException, IOException { 11 12 processRequest(request, response); 13 } 14 15 // 對於不一樣的方法,spring並無作特殊處理,而是統一將程序再一次地引導至processRequest(request, response);中 16 org.springframework.web.servlet.FrameworkServlet類中 17 protected final void processRequest(HttpServletRequest request, HttpServletResponse response) 18 throws ServletException, IOException { 19 20 // 記錄當前時間,用於計算web請求的處理時間 21 long startTime = System.currentTimeMillis(); 22 Throwable failureCause = null; 23 24 LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); 25 LocaleContext localeContext = buildLocaleContext(request); 26 27 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); 28 ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); 29 30 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 31 asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); 32 33 initContextHolders(request, localeContext, requestAttributes); 34 35 try { 36 doService(request, response); 37 } 38 catch (ServletException | IOException ex) { 39 failureCause = ex; 40 throw ex; 41 } 42 catch (Throwable ex) { 43 failureCause = ex; 44 throw new NestedServletException("Request processing failed", ex); 45 } 46 47 finally { 48 resetContextHolders(request, previousLocaleContext, previousAttributes); 49 if (requestAttributes != null) { 50 requestAttributes.requestCompleted(); 51 } 52 53 if (logger.isDebugEnabled()) { 54 if (failureCause != null) { 55 this.logger.debug("Could not complete request", failureCause); 56 } 57 else { 58 if (asyncManager.isConcurrentHandlingStarted()) { 59 logger.debug("Leaving response open for concurrent processing"); 60 } 61 else { 62 this.logger.debug("Successfully completed request"); 63 } 64 } 65 } 66 67 publishRequestHandledEvent(request, response, startTime, failureCause); 68 } 69 }
函數中已經開始了對請求的處理,雖然把細節轉移到了doService函數中實現,可是咱們不難看出處理先後所作的工做
一、爲了保證當前線程的LocaleContext、RequestAttributes能夠在當前請求後還能恢復,提取當前線程的兩個屬性
二、根據當前request建立對應的LocaleContext、RequestAttributes並綁定到當前線程上
三、委託給doService方法進一步處理
四、請求處理結束後恢復線程到原始狀態
五、請求處理結束後不管成功與否發佈事件成功通知服務器
繼續看org.springframework.web.servlet.DispatcherServlet.doService(HttpServletRequest, HttpServletResponse)session
1 @Override 2 protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { 3 if (logger.isDebugEnabled()) { 4 String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : ""; 5 logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed + 6 " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]"); 7 } 8 9 // Keep a snapshot of the request attributes in case of an include, 10 // to be able to restore the original attributes after the include. 11 Map<String, Object> attributesSnapshot = null; 12 if (WebUtils.isIncludeRequest(request)) { 13 attributesSnapshot = new HashMap<>(); 14 Enumeration<?> attrNames = request.getAttributeNames(); 15 while (attrNames.hasMoreElements()) { 16 String attrName = (String) attrNames.nextElement(); 17 if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { 18 attributesSnapshot.put(attrName, request.getAttribute(attrName)); 19 } 20 } 21 } 22 23 // Make framework objects available to handlers and view objects. 24 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); 25 request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); 26 request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); 27 request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); 28 29 if (this.flashMapManager != null) { 30 FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); 31 if (inputFlashMap != null) { 32 request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); 33 } 34 request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); 35 request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); 36 } 37 38 try { 39 doDispatch(request, response); 40 } 41 finally { 42 if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { 43 // Restore the original attribute snapshot, in case of an include. 44 if (attributesSnapshot != null) { 45 restoreAttributesAfterInclude(request, attributesSnapshot); 46 } 47 } 48 } 49 }
spring將已經初始化的功能輔助工具變量,好比,localeResolver、themeResolver、themeSource等設置在request屬性中,看一下處理的方法:
org.springframework.web.servlet.DispatcherServlet.doDispatch(HttpServletRequest, HttpServletResponse):mvc
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 { // 若是是MultipartContext類型的request則轉換request爲MultipartHttpServletRequest類型的 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. // 根據request信息尋找對應的handler mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { // 若是沒有找到handler,則經過response反饋錯誤信息 noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. // 根據當前的Handler尋找對應的HandlerAdapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. // 若是當前handler支持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; } } // 攔截器的preHandler方法的調用 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. // 真正的激活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) { // 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 afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } } // doDispatch方法中展現了spring請求處理所涉及的主要邏輯,下面是邏輯處理全過程的詳細分析:
一、MultipartContext類型的request處理app
對於請求的處理,spring首先考慮的是對於Multipart的處理,若是是MultipartContext類型的request請求,則將request轉化成MultipartHttpServletRequest類型async
org.springframework.web.servlet.DispatcherServlet.checkMultipart(HttpServletRequest)中:ide
1 protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { 2 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { 3 if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { 4 logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " + 5 "this typically results from an additional MultipartFilter in web.xml"); 6 } 7 else if (hasMultipartException(request)) { 8 logger.debug("Multipart resolution previously failed for current request - " + 9 "skipping re-resolution for undisturbed error rendering"); 10 } 11 else { 12 try { 13 return this.multipartResolver.resolveMultipart(request); 14 } 15 catch (MultipartException ex) { 16 if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { 17 logger.debug("Multipart resolution failed for error dispatch", ex); 18 // Keep processing error dispatch with regular request handle below 19 } 20 else { 21 throw ex; 22 } 23 } 24 } 25 } 26 // If not returned before: return original request. 27 return request; 28 }
二、根據request信息查找對應的Handler
在spring中最簡單的映射處理器配置以下:
1 <bean id="simpleUrlHandlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> 2 <property name="mappings"> 3 <props> 4 <prop key="/userlist.htm">userController</prop> 5 </props> 6 </property> 7 </bean>
在spring加載的過程當中,spring會將類型爲SimpleUrlHandlerMapping的實例加載到this.handlerMappings中,按照常理推斷,根據request提取對應的Handler,無非就是提取當前實例中的userController,可是userController爲繼承自AbstractController類型實例,與HandlerExecutionChain並沒有任何關聯,看下這一步如何封裝的:
1 @Nullable 2 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 3 if (this.handlerMappings != null) { 4 for (HandlerMapping hm : this.handlerMappings) { 5 if (logger.isTraceEnabled()) { 6 logger.trace( 7 "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); 8 } 9 HandlerExecutionChain handler = hm.getHandler(request); 10 if (handler != null) { 11 return handler; 12 } 13 } 14 } 15 return null; 16 }
getHandler方法的實現是在org.springframework.web.servlet.handler.AbstractHandlerMapping類中:
spring 5.0 的版本和做者的版本不同,這個邏輯處理是在統一的AbstractHandlerMapping抽象出來的類中,
1 @Override 2 @Nullable 3 public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 4 // 根據request獲取對應的Handler 5 Object handler = getHandlerInternal(request); 6 if (handler == null) { 7 // 若是沒有找到對應的handler 則使用默認的 8 handler = getDefaultHandler(); 9 } 10 if (handler == null) { 11 // 默認的也沒有,則返回null 12 return null; 13 } 14 // Bean name or resolved handler? 15 if (handler instanceof String) { 16 String handlerName = (String) handler; 17 handler = obtainApplicationContext().getBean(handlerName); 18 } 19 // 加入攔截器到執行鏈 20 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); 21 if (CorsUtils.isCorsRequest(request)) { 22 CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request); 23 CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); 24 CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); 25 executionChain = getCorsHandlerExecutionChain(request, executionChain, config); 26 } 27 return executionChain; 28 }
做者推測此方法提供的功能就是根據URL 找到匹配的Controller,最後經過getHandlerExecutionChain對返回的Handler進行封裝,詳細分析一下這個方法:
(1)根據request獲取對應的Handler
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping類中
1 @Override 2 @Nullable 3 protected Object getHandlerInternal(HttpServletRequest request) throws Exception { 4 // 截取用於匹配的URL路徑 5 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); 6 // 根據路徑尋找handler 7 Object handler = lookupHandler(lookupPath, request); 8 if (handler == null) { 9 // We need to care for the default handler directly, since we need to 10 // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well. 11 Object rawHandler = null; 12 if ("/".equals(lookupPath)) { 13 // 若是請求的路徑僅僅是"/",那麼使用RootHandler處理 14 rawHandler = getRootHandler(); 15 } 16 if (rawHandler == null) { 17 // 沒法使用handler 則使用默認的handler 18 rawHandler = getDefaultHandler(); 19 } 20 if (rawHandler != null) { 21 // Bean name or resolved handler? 22 if (rawHandler instanceof String) { 23 // 根據beanName獲取對應的bean 24 String handlerName = (String) rawHandler; 25 rawHandler = obtainApplicationContext().getBean(handlerName); 26 } 27 validateHandler(rawHandler, request); 28 // 模板方法 29 handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); 30 } 31 } 32 if (handler != null && logger.isDebugEnabled()) { 33 logger.debug("Mapping [" + lookupPath + "] to " + handler); 34 } 35 else if (handler == null && logger.isTraceEnabled()) { 36 logger.trace("No handler mapping found for [" + lookupPath + "]"); 37 } 38 return handler; 39 } 40 41 @Nullable 42 protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { 43 // Direct match? 44 // 直接匹配的狀況處理 45 Object handler = this.handlerMap.get(urlPath); 46 if (handler != null) { 47 // Bean name or resolved handler? 48 if (handler instanceof String) { 49 String handlerName = (String) handler; 50 handler = obtainApplicationContext().getBean(handlerName); 51 } 52 validateHandler(handler, request); 53 return buildPathExposingHandler(handler, urlPath, urlPath, null); 54 } 55 56 // Pattern match? 57 // 通配符匹配的處理 58 List<String> matchingPatterns = new ArrayList<>(); 59 for (String registeredPattern : this.handlerMap.keySet()) { 60 if (getPathMatcher().match(registeredPattern, urlPath)) { 61 matchingPatterns.add(registeredPattern); 62 } 63 else if (useTrailingSlashMatch()) { 64 if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) { 65 matchingPatterns.add(registeredPattern + "/"); 66 } 67 } 68 } 69 70 String bestMatch = null; 71 Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath); 72 if (!matchingPatterns.isEmpty()) { 73 matchingPatterns.sort(patternComparator); 74 if (logger.isDebugEnabled()) { 75 logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns); 76 } 77 bestMatch = matchingPatterns.get(0); 78 } 79 if (bestMatch != null) { 80 handler = this.handlerMap.get(bestMatch); 81 if (handler == null) { 82 if (bestMatch.endsWith("/")) { 83 handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); 84 } 85 if (handler == null) { 86 throw new IllegalStateException( 87 "Could not find handler for best pattern match [" + bestMatch + "]"); 88 } 89 } 90 // Bean name or resolved handler? 91 if (handler instanceof String) { 92 String handlerName = (String) handler; 93 handler = obtainApplicationContext().getBean(handlerName); 94 } 95 validateHandler(handler, request); 96 String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath); 97 98 // There might be multiple 'best patterns', let's make sure we have the correct URI template variables 99 // for all of them 100 Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); 101 for (String matchingPattern : matchingPatterns) { 102 if (patternComparator.compare(bestMatch, matchingPattern) == 0) { 103 Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); 104 Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); 105 uriTemplateVariables.putAll(decodedVars); 106 } 107 } 108 if (logger.isDebugEnabled()) { 109 logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables); 110 } 111 return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); 112 } 113 114 // No handler found... 115 return null; 116 } 117 118 // 根據URL獲取對應的Handler的匹配規則代碼實現起來雖然很長,可是並不難理解,考慮了直接匹配與通配符兩種狀況,其中說起的是buildPathExposingHandler, 119 // 他將Handler封裝成HandlerExecutionChain類型 120 121 protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, 122 String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) { 123 124 HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler); 125 chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping)); 126 if (!CollectionUtils.isEmpty(uriTemplateVariables)) { 127 chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); 128 } 129 return chain; 130 }
在函數中,咱們看到了經過將Handler以參數的形式傳入,並構建HandlerExecutionChain類型實例,加入了兩個攔截器,鏈處理機制,是spring中很是經常使用的處理,是AOP中重要組成部分,能夠方便的對目標對象進行擴展和攔截。
(2)加入攔截器到執行鏈
getHandlerExecutionChain函數最主要的目的是將配置中的對應的攔截器加入到執行鏈中,已保證這些攔截器能夠有效的做用於目標對象
1 protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { 2 HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? 3 (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); 4 5 String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); 6 for (HandlerInterceptor interceptor : this.adaptedInterceptors) { 7 if (interceptor instanceof MappedInterceptor) { 8 MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; 9 if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { 10 chain.addInterceptor(mappedInterceptor.getInterceptor()); 11 } 12 } 13 else { 14 chain.addInterceptor(interceptor); 15 } 16 } 17 return chain; 18 }
三、沒找到對應的handler的錯誤處理
每一個請求都會對應着一個handler,由於每一個請求都會在後臺有相應的邏輯對應,而邏輯的實現就是在Handler中,因此一旦遇到沒有找到Handler的狀況(正常狀況下,若是沒有URL匹配的Handler,開發人員能夠設置默認的Handler來處理請求,可是默認狀況也沒有設置,就會出現Handler爲空的狀況),就只能經過response向用戶返回錯誤信息
org.springframework.web.servlet.DispatcherServlet.noHandlerFound(HttpServletRequest, HttpServletResponse):
1 protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception { 2 if (pageNotFoundLogger.isWarnEnabled()) { 3 pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) + 4 "] in DispatcherServlet with name '" + getServletName() + "'"); 5 } 6 if (this.throwExceptionIfNoHandlerFound) { 7 throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request), 8 new ServletServerHttpRequest(request).getHeaders()); 9 } 10 else { 11 response.sendError(HttpServletResponse.SC_NOT_FOUND); 12 } 13 }
四、 根據當前的Handler尋找對應的HandlerAdapter
org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(Object)中:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } if (ha.supports(handler)) { return ha; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); } // 經過上面的函數,無非就是遍歷全部的適配器來選擇合適的適配器並返回它,而某個適配器是否適用於當前的Handler邏輯被封裝在具體的適配器中,進一步查看SimpleControllerHandlerAdapter類中的supports方法 @Override public boolean supports(Object handler) { return (handler instanceof Controller); } // 分析到這裏的話,SimpleControllerHandlerAdapter就是用來處理普通的web請求的,並且對於springMVC來講,咱們會把邏輯封裝在Controller的子類中,也就是開發人員本身定義的Controller類
五、緩存處理
在研究spring對緩存處理的功能支持前,咱們先了解一個概念last-modified緩存機制
(1)在客戶端第一次輸入URL時,服務器端會返回內容和狀態碼200,表示請求成功,同時會添加一個"Last-Modified"的響應頭,表示此文件在服務器上的最後更新時間
(2)客戶端第二次請求URL時,客戶端會向服務端發送請求頭"If-Modified-Since",詢問服務器該時間以後當前請求是否改變過,若是沒有變化,則返回HTTP 304狀態碼
六、HandlerInterceptor的處理
spring API定義的servlet過濾器能夠在servlet處理每一個web請求的先後對它進行前置處理和後置處理,此外,有些時候,你可能只想處理由某些springMVC處理程序處理的web請求,
並在這些處理程序返回的模型屬性被傳遞到視圖以前,對他們進行一些操做
springMVC容許你經過處理攔截web請求,進行前置處理和後置處理。處理攔截是在spring的web應用程序上下文中配置的,所以他們能夠利用各類容器特性。並引用容器中聲明的各類bean,
處理攔截是針對特殊的處理程序映射進行註冊的,所以,他只攔截經過這些處理程序映射的請求。每一個處理攔截都必須實現HandlerInterceptor接口,他包含三個須要實現的回調方法
preHandle、postHandle、afterCompletion,第一個和第二個方法分別是在處理程序處理請求以前和以後被調用的,第二個方法還容許訪問返回ModelAndView對象,所以能夠在它裏面操做模型屬性
七、 邏輯處理
對於邏輯處理實際上是經過適配器中 轉調用Handler並返回視圖的,看一下SimpleControllerHandlerAdapter中的實現:
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter中
1 @Override 2 @Nullable 3 public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) 4 throws Exception { 5 6 return ((Controller) handler).handleRequest(request, response); 7 } 8 9 org.springframework.web.servlet.mvc.AbstractController中 10 @Override 11 @Nullable 12 public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) 13 throws Exception { 14 15 if (HttpMethod.OPTIONS.matches(request.getMethod())) { 16 response.setHeader("Allow", getAllowHeader()); 17 return null; 18 } 19 20 // Delegate to WebContentGenerator for checking and preparing. 21 checkRequest(request); 22 prepareResponse(response); 23 24 // Execute handleRequestInternal in synchronized block if required. 25 // 若是須要session內的同步執行 26 if (this.synchronizeOnSession) { 27 HttpSession session = request.getSession(false); 28 if (session != null) { 29 Object mutex = WebUtils.getSessionMutex(session); 30 synchronized (mutex) { 31 // 調用客戶的邏輯 32 return handleRequestInternal(request, response); 33 } 34 } 35 } 36 37 // 調用客戶的邏輯 38 return handleRequestInternal(request, response); 39 }
八、異常視圖的處理
1 // processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 2 3 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, 4 @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, 5 @Nullable Exception exception) throws Exception { 6 7 boolean errorView = false; 8 9 if (exception != null) { 10 if (exception instanceof ModelAndViewDefiningException) { 11 logger.debug("ModelAndViewDefiningException encountered", exception); 12 mv = ((ModelAndViewDefiningException) exception).getModelAndView(); 13 } 14 else { 15 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); 16 mv = processHandlerException(request, response, handler, exception); 17 errorView = (mv != null); 18 } 19 } 20 21 // Did the handler return a view to render? 22 if (mv != null && !mv.wasCleared()) { 23 // 根據視圖頁面 24 render(mv, request, response); 25 if (errorView) { 26 WebUtils.clearErrorRequestAttributes(request); 27 } 28 } 29 else { 30 if (logger.isDebugEnabled()) { 31 logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + 32 "': assuming HandlerAdapter completed request handling"); 33 } 34 } 35 36 if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { 37 // Concurrent handling started during a forward 38 return; 39 } 40 41 if (mappedHandler != null) { 42 mappedHandler.triggerAfterCompletion(request, response, null); 43 } 44 } 45 46 // processHandlerException方法中異常視圖的處理 47 @Nullable 48 protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, 49 @Nullable Object handler, Exception ex) throws Exception { 50 51 // Check registered HandlerExceptionResolvers... 52 ModelAndView exMv = null; 53 if (this.handlerExceptionResolvers != null) { 54 for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) { 55 exMv = handlerExceptionResolver.resolveException(request, response, handler, ex); 56 if (exMv != null) { 57 break; 58 } 59 } 60 } 61 if (exMv != null) { 62 if (exMv.isEmpty()) { 63 request.setAttribute(EXCEPTION_ATTRIBUTE, ex); 64 return null; 65 } 66 // We might still need view name translation for a plain error model... 67 if (!exMv.hasView()) { 68 String defaultViewName = getDefaultViewName(request); 69 if (defaultViewName != null) { 70 exMv.setViewName(defaultViewName); 71 } 72 } 73 if (logger.isDebugEnabled()) { 74 logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex); 75 } 76 WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); 77 return exMv; 78 } 79 80 throw ex; 81 }
九、根據視圖跳轉頁面
processDispatchResult方法中有一個方法是實現頁面跳轉的邏輯的,就是render(mv, request, response);看這個方法的邏輯實現:
org.springframework.web.servlet.DispatcherServlet類中:
1 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { 2 // Determine locale for request and apply it to the response. 3 Locale locale = 4 (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); 5 response.setLocale(locale); 6 7 View view; 8 String viewName = mv.getViewName(); 9 if (viewName != null) { 10 // We need to resolve the view name. 11 // 解析視圖名稱 12 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); 13 if (view == null) { 14 throw new ServletException("Could not resolve view with name '" + mv.getViewName() + 15 "' in servlet with name '" + getServletName() + "'"); 16 } 17 } 18 else { 19 // No need to lookup: the ModelAndView object contains the actual View object. 20 view = mv.getView(); 21 if (view == null) { 22 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + 23 "View object in servlet with name '" + getServletName() + "'"); 24 } 25 } 26 27 // Delegate to the View object for rendering. 28 if (logger.isDebugEnabled()) { 29 logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); 30 } 31 try { 32 if (mv.getStatus() != null) { 33 response.setStatus(mv.getStatus().value()); 34 } 35 // 頁面跳轉 36 view.render(mv.getModelInternal(), request, response); 37 } 38 catch (Exception ex) { 39 if (logger.isDebugEnabled()) { 40 logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + 41 getServletName() + "'", ex); 42 } 43 throw ex; 44 } 45 }
(1)解析視圖名稱
在上文中咱們提到DispatcherServlet會根據ModelAndView選擇合適的視圖來進行渲染,而這一功能就是在resolveViewName中完成的
1 protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, 2 Locale locale, HttpServletRequest request) throws Exception { 3 4 if (this.viewResolvers != null) { 5 for (ViewResolver viewResolver : this.viewResolvers) { 6 View view = viewResolver.resolveViewName(viewName, locale); 7 if (view != null) { 8 return view; 9 } 10 } 11 } 12 return null; 13 } 14 // 咱們以org.springframework.web.servlet.view.AbstractCachingViewResolver.resolveViewName(String, Locale)爲例子分析ViewResolver邏輯的解析過程, 15 16 @Override 17 @Nullable 18 public View resolveViewName(String viewName, Locale locale) throws Exception { 19 if (!isCache()) { 20 // 不存在緩存的狀況下直接 21 return createView(viewName, locale); 22 } 23 else { 24 // 直接從緩存中提取 25 Object cacheKey = getCacheKey(viewName, locale); 26 View view = this.viewAccessCache.get(cacheKey); 27 if (view == null) { 28 synchronized (this.viewCreationCache) { 29 view = this.viewCreationCache.get(cacheKey); 30 if (view == null) { 31 // Ask the subclass to create the View object. 32 view = createView(viewName, locale); 33 if (view == null && this.cacheUnresolved) { 34 view = UNRESOLVED_VIEW; 35 } 36 if (view != null) { 37 this.viewAccessCache.put(cacheKey, view); 38 this.viewCreationCache.put(cacheKey, view); 39 if (logger.isTraceEnabled()) { 40 logger.trace("Cached view [" + cacheKey + "]"); 41 } 42 } 43 } 44 } 45 } 46 return (view != UNRESOLVED_VIEW ? view : null); 47 } 48 } 49 50 // org.springframework.web.servlet.view.UrlBasedViewResolver中重寫的createView方法 51 @Override 52 protected View createView(String viewName, Locale locale) throws Exception { 53 // If this resolver is not supposed to handle the given view, 54 // return null to pass on to the next resolver in the chain. 55 // 若是當前解析器不支持當前解析器 如viewName爲空 56 if (!canHandle(viewName, locale)) { 57 return null; 58 } 59 60 // Check for special "redirect:" prefix. 61 // 處理前綴爲redirect:x的狀況 62 if (viewName.startsWith(REDIRECT_URL_PREFIX)) { 63 String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); 64 RedirectView view = new RedirectView(redirectUrl, 65 isRedirectContextRelative(), isRedirectHttp10Compatible()); 66 String[] hosts = getRedirectHosts(); 67 if (hosts != null) { 68 view.setHosts(hosts); 69 } 70 return applyLifecycleMethods(REDIRECT_URL_PREFIX, view); 71 } 72 73 // Check for special "forward:" prefix. 74 // 處理前綴爲forward:x的狀況 75 if (viewName.startsWith(FORWARD_URL_PREFIX)) { 76 String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); 77 return new InternalResourceView(forwardUrl); 78 } 79 80 // Else fall back to superclass implementation: calling loadView. 81 return super.createView(viewName, locale); 82 } 83 84 //org.springframework.web.servlet.view.AbstractCachingViewResolver父類中的createView 85 @Nullable 86 protected View createView(String viewName, Locale locale) throws Exception { 87 return loadView(viewName, locale); 88 } 89 90 // org.springframework.web.servlet.view.UrlBasedViewResolver子類中重寫loadView 91 @Override 92 protected View loadView(String viewName, Locale locale) throws Exception { 93 AbstractUrlBasedView view = buildView(viewName); 94 View result = applyLifecycleMethods(viewName, view); 95 return (view.checkResource(locale) ? result : null); 96 } 97 98 protected AbstractUrlBasedView buildView(String viewName) throws Exception { 99 Class<?> viewClass = getViewClass(); 100 Assert.state(viewClass != null, "No view class"); 101 102 AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass); 103 // 添加前綴以及後綴 104 view.setUrl(getPrefix() + viewName + getSuffix()); 105 106 String contentType = getContentType(); 107 if (contentType != null) { 108 // 設置ContentType 109 view.setContentType(contentType); 110 } 111 112 view.setRequestContextAttribute(getRequestContextAttribute()); 113 view.setAttributesMap(getAttributesMap()); 114 115 Boolean exposePathVariables = getExposePathVariables(); 116 if (exposePathVariables != null) { 117 view.setExposePathVariables(exposePathVariables); 118 } 119 Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes(); 120 if (exposeContextBeansAsAttributes != null) { 121 view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes); 122 } 123 String[] exposedContextBeanNames = getExposedContextBeanNames(); 124 if (exposedContextBeanNames != null) { 125 view.setExposedContextBeanNames(exposedContextBeanNames); 126 } 127 128 return view; 129 }
(2)頁面跳轉
當viewName解析到對應的view以後,就能夠進一步的處理跳轉邏輯了
org.springframework.web.servlet.view.AbstractView類重寫了render方法
1 @Override 2 public void render(@Nullable Map<String, ?> model, HttpServletRequest request, 3 HttpServletResponse response) throws Exception { 4 5 if (logger.isTraceEnabled()) { 6 logger.trace("Rendering view with name '" + this.beanName + "' with model " + model + 7 " and static attributes " + this.staticAttributes); 8 } 9 10 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); 11 prepareResponse(request, response); 12 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); 13 } 14 15 就是把一些須要用到的屬性放到request中,createMergedOutputModel就是完成這個操做的 16 protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model, 17 HttpServletRequest request, HttpServletResponse response) { 18 19 @SuppressWarnings("unchecked") 20 Map<String, Object> pathVars = (this.exposePathVariables ? 21 (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null); 22 23 // Consolidate static and dynamic model attributes. 24 int size = this.staticAttributes.size(); 25 size += (model != null ? model.size() : 0); 26 size += (pathVars != null ? pathVars.size() : 0); 27 28 Map<String, Object> mergedModel = new LinkedHashMap<>(size); 29 mergedModel.putAll(this.staticAttributes); 30 if (pathVars != null) { 31 mergedModel.putAll(pathVars); 32 } 33 if (model != null) { 34 mergedModel.putAll(model); 35 } 36 37 // Expose RequestContext? 38 if (this.requestContextAttribute != null) { 39 mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel)); 40 } 41 42 return mergedModel; 43 }