springmvc支持服務端在處理業務邏輯過程當中出現異常的時候能夠配置相應的ModelAndView對象返回給客戶端,本文介紹springmvc默認的幾種HandlerExceptionResolver類html
springmvc的xml配置化-Exception配置java
<bean id="exceptionHandler" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!--設置默認返回viewName,一般與freemarker引擎搭配使用--> <property name="defaultErrorView" value="error/defaultError" /> <!--設置默認返回response status--> <property name="defaultStatusCode" value="500" /> <!--配置相應的異常類與viewName的映射--> <property name="exceptionMappings"> <props> <prop key="SessionTimeoutException">redirect:../login.html</prop> <prop key="AuthenticationException">error/403</prop> </props> </property> </bean>
以上的配置會對出SessionTimeoutException異常則跳轉至login頁面,對AuthenticationException異常則跳轉至403頁面,對其餘的異常則默認跳轉至defaultError頁面呈現並返回500的錯誤碼web
接口內只有一個方法resolveException()
,經過解析異常查詢配置以獲得符合條件的ModelAndView對象spring
/** * Try to resolve the given exception that got thrown during handler execution, * returning a {@link ModelAndView} that represents a specific error page if appropriate. * <p>The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty} * to indicate that the exception has been resolved successfully but that no view * should be rendered, for instance by setting a status code. * @param request current HTTP request * @param response current HTTP response * @param handler the executed handler, or {@code null} if none chosen at the * time of the exception (for example, if multipart resolution failed) * @param ex the exception that got thrown during handler execution * @return a corresponding {@code ModelAndView} to forward to, or {@code null} * for default processing */ ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
全部的spring內置異常解析類都繼承於此,直奔主題看resolveException()方法json
@Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //判斷是否須要解析 if (shouldApplyTo(request, handler)) { //此處通常是判斷內部屬性preventResponseCaching是否爲true,是則設置響應包頭cache-control:no-store prepareResponse(ex, response); //使用模板方法doResolveException()方法供子類實現 ModelAndView result = doResolveException(request, response, handler, ex); if (result != null) { //日誌打印一發 logException(ex, request); } return result; } else { return null; } }
附帶着分析下shouldApplyTo()方法mvc
/** **能夠配置mappedHandlers和mappedHandlerClasses屬性來特定匹配 **默認狀況下二者都爲空則直接返回true,代表對全部的handler都進行異常解析 */ protected boolean shouldApplyTo(HttpServletRequest request, Object handler) { //此處的handler通常爲bean對象 if (handler != null) { if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) { return true; } if (this.mappedHandlerClasses != null) { for (Class<?> handlerClass : this.mappedHandlerClasses) { if (handlerClass.isInstance(handler)) { return true; } } } } // Else only apply if there are no explicit handler mappings. return (this.mappedHandlers == null && this.mappedHandlerClasses == null); }
比較簡單的實現類,能夠配綁定viewName和exception以完成簡單的異常映射視圖頁面app
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // Expose ModelAndView for chosen error view. String viewName = determineViewName(ex, request); if (viewName != null) { //若是配置了statusCodes屬性,則對此異常的狀態碼進行設置 Integer statusCode = determineStatusCode(request, viewName); if (statusCode != null) { applyStatusCodeIfPossible(request, response, statusCode); } //建立ModelAndView對象 return getModelAndView(viewName, ex, request); } else { return null; } }
針對以上的源碼咱們分兩步去簡單分析下異步
protected String determineViewName(Exception ex, HttpServletRequest request) { String viewName = null; //判斷異常是否屬於excludeExceptions集合內,是則直接返回null if (this.excludedExceptions != null) { for (Class<?> excludedEx : this.excludedExceptions) { if (excludedEx.equals(ex.getClass())) { return null; } } } // Check for specific exception mappings. // 從exceptionMappings集合內根據exception獲取到相應的viewName if (this.exceptionMappings != null) { viewName = findMatchingViewName(this.exceptionMappings, ex); } //當exceptionMappings集合內不存在指定的exception可是默認視圖指定則直接返回默認視圖 if (viewName == null && this.defaultErrorView != null) { viewName = this.defaultErrorView; } return viewName; }
excludedExceptions集合能夠過濾指定的exception,對其不進行解析直接返回null。可配置ide
exceptionMappings綁定了exception與viewName的關係,若是在其集合內沒找到相應的viewName,可是defaultErrorView屬性指定,則會直接返回defaultErrorView對應的視圖this
protected ModelAndView getModelAndView(String viewName, Exception ex) { ModelAndView mv = new ModelAndView(viewName); //exceptionAttribute默認爲exception if (this.exceptionAttribute != null) { //將exception信息添加到model中 mv.addObject(this.exceptionAttribute, ex); } return mv; }
主要就是將exceptionAttribute對應的參數值默認爲
exception
屬性添加到視圖對象的model中
主要是解析帶有@ResponseStatus
的異常類,將其中的異常信息描述直接返回給客戶端
@Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //獲取相應類上的註解@ResponseStatus ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); if (responseStatus != null) { try { return resolveResponseStatus(responseStatus, request, response, handler, ex); } catch (Exception resolveEx) { logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx); } } else if (ex.getCause() instanceof Exception) { ex = (Exception) ex.getCause(); //遞歸 return doResolveException(request, response, handler, ex); } return null; }
讀取@ResponseStatus
註解信息,返回異常內容給客戶端
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //狀態碼 int statusCode = responseStatus.code().value(); //異常緣由描述 String reason = responseStatus.reason(); if (this.messageSource != null) { reason = this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()); } //經過response對象直接返回錯誤信息給客戶端 if (!StringUtils.hasLength(reason)) { response.sendError(statusCode); } else { //經過response對象直接返回錯誤信息給客戶端 response.sendError(statusCode, reason); } return new ModelAndView(); }
源碼就不公佈了,讀者可自行去查詢,基本都是調用response的sendError()方法返回錯誤信息給客戶端。本文對其中的異常歸下類
- 請求方式異常
- HttpRequestMethodNotSupportedException-服務端不支持相應的請求方法
- HttpMediaTypeNotSupportedException/HttpMediaTypeNotAcceptableException-服務端/客戶端不支持相應的mediaType,好比
application/json
- MissingPathVariableException-
@PathVaribale
指定參數請求中不包含- MissingServletRequestParameterException/ServletRequestBindingException-請求參數綁定錯誤
- MethodArgumentNotValidException-
@Valid
註解指定的參數校驗失敗- AsyncRequestTimeoutException-異步請求超時
- 消息內容異常
- ConversionNotSupportedException-服務端找尋不到相應的Convert對象來解析javabean
- TypeMismatchException-設置javabean屬性類型出錯
- HttpMessageNotReadableException/HttpMessageNotWritableException-消息內容不可讀/不可寫
- MissingServletRequestPartException-文件上傳類錯誤,可能請求沒有
multipart/form-data
或者服務不支持文件上傳- NoHandlerFoundException-handler處理器沒有找到,便可能沒有對應的請求處理供響應
具體的邏輯本文則不展開了,簡述下其中的邏輯:
當處理handlerMethod業務邏輯過程當中出現了異常,則此解析器
嘗試從handlerMethod所在的class類去找尋是否含有
@ExceptionHandler
註解的方法判斷
@ExceptionHandler
指定的exception類與產生的異常一致,一致則執行相應的方法,當有多個@ExceptionHandler(value)
,則默認採用第一個當上述在class類找尋不到則嘗試判斷class類是否含有
@ControllerAdvice
註解,有則按照上述第一二步的步驟再次找尋@ControllerAdvice
指定的類
springmvc開放了對異常也能夠包裝成頁面顯示的功能,經過本文的簡單分析能夠幫助博主和讀者更好的理解springmvc對異常的處理