springmvc支持服務端在處理業務邏輯過程中出現異常的時候可以配置相應的ModelAndView對象返回給客戶端,本文介紹springmvc默認的幾種HandlerExceptionResolver類
實際應用
springmvc的xml配置化-Exception配置
<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的錯誤碼
HandlerExceptionResolver-異常解析接口
接口內只有一個方法resolveException()
,通過解析異常查詢配置以得到符合條件的ModelAndView對象
/**
* 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);
AbstractHandlerExceptionResolver-異常解析抽象基類
所有的spring內置異常解析類都繼承於此,直奔主題看resolveException()方法
@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()方法
/**
**可以配置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);
}
1. SimpleMappingExceptionResolver-異常映射實現類
比較簡單的實現類,可以配綁定viewName和exception以完成簡單的異常映射視圖頁面
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;
}
}
針對以上的源碼我們分兩步去簡單分析下
SimpleMappingExceptionResolver#determineViewName()-找尋viewName
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。可配置
exceptionMappings綁定了exception與viewName的關系,如果在其集合內沒找到相應的viewName,但是defaultErrorView屬性指定,則會直接返回defaultErrorView對應的視圖
SimpleMappingExceptionResolver#getModelAndView()-創建ModelAndView對象
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中
2. ResponseStatusExceptionResolver-響應狀態異常解析類
主要是解析帶有@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;
}
ResponseStatusExceptionResolver#resolveResponseStatus()-返回異常信息給客戶端
讀取@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();
}
3. DefaultHandlerExceptionResolver-springmvc默認的異常解析處理
源碼就不公布了,讀者可自行去查詢,基本都是調用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處理器沒有找到,即可能沒有對應的請求處理供響應
4. ExceptionHandlerExceptionResolver-處理handlerMethod對象過程中的異常
具體的邏輯本文則不展開了,簡述下其中的邏輯:
當處理handlerMethod業務邏輯過程中出現了異常,則此解析器
嘗試從handlerMethod所在的class類去找尋是否含有
@ExceptionHandler
注解的方法判斷
@ExceptionHandler
指定的exception類與產生的異常一致,一致則執行相應的方法,當有多個@ExceptionHandler(value)
,則默認采用第一個當上述在class類找尋不到則嘗試判斷class類是否含有
@ControllerAdvice
注解,有則按照上述第一二步的步驟再次找尋@ControllerAdvice
指定的類
小結
springmvc開放了對異常也可以包裝成頁面顯示的功能,通過本文的簡單分析可以幫助博主和讀者更好的理解springmvc對異常的處理