本篇將分析一次請求從接收處處理的最終環節——視圖處理,也是 SpringMVC 源碼解析的最後一節。將涉及異常處理和視圖轉發兩部分。java
承接上篇,來看 「processDispatchResult」 的實現。web
public class DispatcherServlet extends FrameworkServlet { 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 (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { return; } if (mappedHandler != null) { // 調用攔截器的 afterCompletion mappedHandler.triggerAfterCompletion(request, response, null); } } }
總體來看,該方法有三個主要步驟:異常處理、視圖轉發、攔截器調用。其中攔截器調用邏輯簡單,就是遍歷調用攔截器的 afterCompletion 方法。因此重點來看前二者的實現。數組
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { ModelAndView exMv = null; // 遍歷全部註冊的 HandlerExceptionResolver(按優先級),調用其 resolveException // 直到有一個解析器處理後返回非空的 ModelAndView for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) { exMv = handlerExceptionResolver.resolveException(request, response, handler, ex); if (exMv != null) { break; } } // 若是 exMv != null,說明被解析器捕獲並處理 if (exMv != null) { if (exMv.isEmpty()) { request.setAttribute(EXCEPTION_ATTRIBUTE, ex); return null; } // 若是被處理返回的 ModelAndView未指定頁面,則使用默認頁面 if (!exMv.hasView()) { exMv.setViewName(getDefaultViewName(request)); } ...// 省略日誌 WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); // 返回異常處理視圖 return exMv; } // 沒有被捕獲的就會拋出異常 throw ex; }
這裏的 HandlerExceptionResolver 就是 Dispatcher 初始化策略中,initHandlerExceptionResolvers 方法中註冊的,默認會掃描註冊全局 HandlerExceptionResolver 類型的實例。先來回顧下 SpringMVC 處理異常的幾種方式:緩存
方式一可能比較侷限,一個類只能針對某種類型異常的處理;方式2、方式三比較方便,共同點都是藉助了 @ExceptionHandler 註解,value 能夠指定異常類型數組;方式四通常用於異常是自定義的場景;方式五是配置方式的,能夠經過基於 xml 文件配置,也能夠經過 SpringBoot 的基於代碼配置。併發
這裏僅分析下來方式二、方式三的實現原理,這也是比較經常使用的兩種方式:app
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 判斷是否支持該 Handler的異常處理 if (shouldApplyTo(request, handler)) { if (this.logger.isDebugEnabled()) { this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex); } // 是否避免返回的頁面被緩存,默認爲 false // 避免緩存是經過添加「Cache-Control=no-store」實現的 prepareResponse(ex, response); // 該方法交由子類擴展 ModelAndView result = doResolveException(request, response, handler, ex); if (result != null) { // 日誌記錄 logException(ex, request); } return result; } else { return null; } } protected boolean shouldApplyTo(HttpServletRequest request, Object handler) { if (handler != null) { // 首先判斷 mappedHandlers是否包含此 Handler,由 setMappedHandlers方法指定 if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) { return true; } // 再判斷 mappedHandlerClasses是否指定了該 Handler類型,由 setMappedHandlerClasses方法指定 if (this.mappedHandlerClasses != null) { for (Class<?> handlerClass : this.mappedHandlerClasses) { if (handlerClass.isInstance(handler)) { return true; } } } } // 若是不指定,默認 mappedHandlers和 mappedHandlerClasses都爲 null,所以返回 true return (this.mappedHandlers == null && this.mappedHandlerClasses == null); } }
AbstractHandlerExceptionResolver 是框架層面全部異常解析器的公共抽象父類,實現了resolveException 方法,首先判斷下是否支持該 Handler 的異常處理,若是不指定 mappedHandlers、mappedHandlerClasses,默認支持所有 Handler 的異常處理。框架
具體異常處理的邏輯以抽象方法(doResolveException)的形式交由子類實現。jsp
@ExceptionHandler 本來的支持類爲 AnnotationMethodHandlerExceptionResolver,該類在 Spring 3.2 版本後聲明廢棄,代替者爲 ExceptionHandlerExceptionResolver。ide
AbstractHandlerMethodExceptionResolver 做爲 ExceptionHandlerExceptionResolver 的抽象父類,實現 doResolveException。this
public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver { @Override protected final ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 抽象方法:子類實現 return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex); } }
這個方法這是作了次參數的向下轉型,進而調用 doResolveHandlerMethodException ,這個方法由 ExceptionHandlerExceptionResolver 實現。(這裏又看到了 「HandlerMethod 」 的身影)
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean { @Override protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) { // 跟請求處理相同的套路,一樣使用 ServletInvocableHandlerMethod ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); if (exceptionHandlerMethod == null) { return null; } // 入參和返回解析器設置 exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); ServletWebRequest webRequest = new ServletWebRequest(request, response); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); try { if (logger.isDebugEnabled()) { logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod); } Throwable cause = exception.getCause(); if (cause != null) { // 調用邏輯見請求處理章節 exceptionHandlerMethod.invokeAndHandle( webRequest, mavContainer, exception, cause, handlerMethod); } else { exceptionHandlerMethod.invokeAndHandle( webRequest, mavContainer, exception, handlerMethod); } } catch (Throwable invocationEx) { // 有可能在調用 Handler處理時拋出異常,例如斷言判空之類的 if (invocationEx != exception && logger.isWarnEnabled()) { logger.warn("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx); } return null; } // 判斷請求是否已在 Handler中徹底處理 if (mavContainer.isRequestHandled()) { return new ModelAndView(); } else { // 將 Handler處理的結果封裝成 ModelAndView返回 ModelMap model = mavContainer.getModel(); HttpStatus status = mavContainer.getStatus(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status); mav.setViewName(mavContainer.getViewName()); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); request = webRequest.getNativeRequest(HttpServletRequest.class); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } } }
剛纔提到的異常處理方式2、方式三,說到底也是一次 Method 的調用過程:就像請求經過映射關係找到對應的 HandlerMethod 同樣,ExceptionHandlerExceptionResolver 經過定義的異常類型來找到對應的 HandlerMethod,進而調用 invokeAndHandle 來反射調用(裏面包含的參數解析、反射調用等,在以前的章節已分析過)。
這裏重點來分析下 getExceptionHandlerMethod 尋找 HandlerMethod 的邏輯。
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) { // 首先獲取拋出異常點(業務處理方法)所在類的類型 Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null); if (handlerMethod != null) { // 先從緩存中取,若是沒有則建立一個並放入緩存 ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType); if (resolver == null) { resolver = new ExceptionHandlerMethodResolver(handlerType); this.exceptionHandlerCache.put(handlerType, resolver); } // 關注此方法:解析出處理該異常的方法 Method method = resolver.resolveMethod(exception); // 若是不爲 null,說明拋異常的方法所在類下,正好有一個被 @ExceptionHandler註解標識的方法對應處理該異常 if (method != null) { // 包裝一個 ServletInvocableHandlerMethod返回 return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method); } } // 若是對應類下沒有能力捕獲到該異常,就遍歷全部 @ControllerAdvice標識的類下,指定的 @ExceptionHandler for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { // 判斷是否支持傳入類型的加強 // 若是 @ControllerAdvice沒有指定 basePackages、basePackageClasses、annotations、assignableTypes,則返回 true,即全局加強 // 若是指定了上述的順序,逐個篩選,知足其一就返回 true if (entry.getKey().isApplicableToBeanType(handlerType)) { ExceptionHandlerMethodResolver resolver = entry.getValue(); // 關注此方法:解析出處理該異常的方法 Method method = resolver.resolveMethod(exception); if (method != null) { // 包裝一個 ServletInvocableHandlerMethod返回 return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method); } } } return null; }
從源碼能夠看出,首先會從異常拋出所在類中找處理方法(方式二),沒有才會到全局加強中找(方式三)。這裏使用到的緩存 exceptionHandlerAdviceCache 初始化藉助了生命週期接口實現 InitializingBean .afterPropertiesSet,該方法調用了 initExceptionHandlerAdviceCache 將全局被 @ControllerAdvice 標識的類實例放入 exceptionHandlerAdviceCache。
接着來看下二者共同使用的 ExceptionHandlerMethodResolver 是如何經過異常解析出 Method,上面代碼經過 new ExceptionHandlerMethodResolver(handlerType) 建立了該類的實例,該構造器將會初始化異常和處理方法的映射關係,源碼以下:
public class ExceptionHandlerMethodResolver { // 存放異常和處理方法的映射關係 private final Map<Class<? extends Throwable>, Method> mappedMethods = new ConcurrentHashMap<Class<? extends Throwable>, Method>(16); public ExceptionHandlerMethodResolver(Class<?> handlerType) { // 遍歷 Handler下的全部方法,找到被 @ExceptionHandler標識的方法 for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { // 遍歷方法指定處理的異常類型(Throwable類型) for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) { // 註冊異常和對應處理方法的映射關係 addExceptionMapping(exceptionType, method); } } } private List<Class<? extends Throwable>> detectExceptionMappings(Method method) { List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>(); // 找方法上 @ExceptionHandler 註解聲明的異常類型 detectAnnotationExceptionMappings(method, result); // 註解未指定,則會將方法入參爲 Throwable類型填充 result if (result.isEmpty()) { for (Class<?> paramType : method.getParameterTypes()) { if (Throwable.class.isAssignableFrom(paramType)) { result.add((Class<? extends Throwable>) paramType); } } } // 若是註解和方法都沒有指定異常類型,拋出異常 if (result.isEmpty()) { throw new IllegalStateException("No exception types mapped to " + method); } return result; } private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) { // 關聯關係 Method oldMethod = this.mappedMethods.put(exceptionType, method); // 防止一個異常對應多個處理方法 if (oldMethod != null && !oldMethod.equals(method)) { throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" + oldMethod + ", " + method + "}"); } } }
須要注意,這裏的 detectExceptionMappings 若是註解指定了異常類型,就不會再考慮方法入參的異常類型了。
映射關係創建以後,繼續看解析邏輯:
public Method resolveMethod(Exception exception) { Method method = resolveMethodByExceptionType(exception.getClass()); if (method == null) { Throwable cause = exception.getCause(); if (cause != null) { method = resolveMethodByExceptionType(cause.getClass()); } } return method; } public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) { // 首先從緩存中獲取 Method method = this.exceptionLookupCache.get(exceptionType); if (method == null) { // 緩存中沒有,則從 mappedMethods中獲取 method = getMappedMethod(exceptionType); // 放入緩存 this.exceptionLookupCache.put(exceptionType, (method != null ? method : NO_METHOD_FOUND)); } return (method != NO_METHOD_FOUND ? method : null); }
這裏可能有人會疑問,以前已經創建了異常和處理方法的映射關係,爲何又維護了一個 exceptionLookupCache 呢?
咱們來假象一種場景:某方法拋出的異常 A,A 繼承自類型 B。咱們針對異常 A 和 B 分別定義了處理方法,那麼究竟哪一個方法會處理異常 A?
所以就有一個篩選邏輯,邏輯見 getMappedMethod 方法,使用 ExceptionDepthComparator 比較器進行最優處理方法挑選(即挑選聲明異常越具體的方法)。這裏不作展開分析。
到此爲止,異常處理已經分析完成。
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // 使用 initLocaleResolver初始化的 LocaleResolver進行國際化處理(默認:AcceptHeaderLocaleResolver) // 肯定請求的語言環境並將其應用於響應 Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); View view; String viewName = mv.getViewName(); if (viewName != null) { // 遍歷註冊的 ViewResolver,調用resolveViewName解析 viewName爲 View 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 { // 若是沒有指定 viewName,則直接獲取視圖 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() + "'"); } } ....// 省略日誌 try { if (mv.getStatus() != null) { // 響應碼設置 response.setStatus(mv.getStatus().value()); } // 調用 View.render view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { ....// 省略日誌 throw ex; } }
涉及到了國際化處理(LocaleResolver),視圖解析(ViewResolver)。其中 resolveViewName 遍歷了全部註冊的 ViewResolver,將 viewName 轉換爲 View。以 UrlBasedViewResolver 爲例,它會將指定的 prefix、suffix 先後綴與 viewName 拼接後建立 View。
public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware { @Override public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { ....// 省略日誌 // 將 View實例配置中的固定屬性和 Model中的動態屬性合併到一塊兒 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); // 子類擴展 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); } }
renderMergedOutputModel 方法由子類實現。
public class InternalResourceView extends AbstractUrlBasedView { @Override protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // 調用 setAttribute將 model中屬性設置到 request中 exposeModelAsRequestAttributes(model, request); exposeHelpers(request); // 肯定請求分派器的路徑 String dispatcherPath = prepareForRendering(request, response); // 獲取請求的 RequestDispatcher RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } // 判斷當前 response是否已經提交 if (useInclude(request, response)) { response.setContentType(getContentType()); ....// 省略日誌 // 調用 RequestDispatcher.include,見 <jsp:include> // 該方法調用後,原 Servlet仍對請求有主導權,並將新的資源包含到當前響應當中 rd.include(request, response); } else { ....// 省略日誌 // 調用 RequestDispatcher.forward // 該方法將請求直接轉發給其餘 Servlet處理 rd.forward(request, response); } } }
這裏以 InternalResourceView 爲例,JSP 就是經過這種方式實現的。JSP 全稱 Java Servlet Page,底層就是將所寫的 JSP 頁面編譯成 Servlet 字節碼文件,這裏的轉發也就是將請求轉發給這些對應的 Servlet 。
固然除了 InternalResourceView ,還有像 MappingJackson2JsonView(支持 @JsonView 註解)等。以後的邏輯就交由 Servlet 容器(像 Tomcat)的實現了。
到此爲止,Spring 從容器啓動到一次請求處理就所有解析完畢了。