一.概述
1.1 異常處理概述
SpringMVC通過HandlerExceptionResolver處理程序的異常,包括Handler映射,數據綁定以及目標方法執行時發生的異常。
SpringMVC提供的HandlerExceptionResolver的實現類:
DispatcherServlet默認裝配HandlerExceptionResolver:
沒有使用<mvc:annotation-driven/>配置:
使用了<mvc:annotation-driven/>配置:
二. 源碼分析
在來到頁面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);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//渲染頁面
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
處理異常的方法:
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
exMv.setViewName(getDefaultViewName(request));
}
if (logger.isDebugEnabled()) {
logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
異常解析器都不能處理,就直接拋出異常到afterCompletion方法,直接到異常頁面。
三.異常處理解析器
3.1 ExceptionHandlerExceptionResolver
3.1.1 概述
ExceptionHandlerExceptionResolver主要處理Handler中用@ExceptionHandler註解定義的方法。
@ExceptionHandler註解定義的方法優先級問題:例如發生的是NullPointerException,但是聲明的異常有RuntimeException和Exception,此時會根據異常的最近繼承關係找到繼承深度最淺的那個@ExceptionHandler註解方法,即標註了RuntimeException的方法。
ExceptionHandlerExceptionResolver內部如果找不到@ExceptionHandler註解的話,會找@ControllerAdvice中的@ExceptionHandler註解方法。
3.1.2 實驗代碼:將異常對象從控制器攜帶給頁面,做異常信息的獲取
1.頁面鏈接:
<a href="${ctp }/handle01?i=0">測試</a>
2.控制器方法:在控制器中增加處理異常的方法,在這個方法中將異常對象從控制器攜帶給頁面。注意異常對象不能通過Map集合方式傳遞給成功頁面,而是通過ModelAndView對象進行傳遞。
@RequestMapping("/handle01")
public String handle01(Integer i){
System.out.println("hanle01...");
System.out.println(10/i);
return "success";
}
/*
* 告訴SpringMVC這個方法專門處理這個類發生的異常
* 1.給方法上隨便寫一個Exception,用來接受發生的異常
* 2.要攜帶異常信息不能給參數位置寫Model
* 3.返回ModelAndView
* 4.如果有多個@ExceptionHandler都能處理這個異常,精確優先
*
*/
@ExceptionHandler(value={ArithmeticException.class})
public ModelAndView handleException01(Exception exception){
System.out.println("handleException01...");
ModelAndView modelAndView=new ModelAndView("myerror");
modelAndView.addObject("ex", exception);
//視圖解析器拼串
return modelAndView;
}
3.錯誤頁面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>出錯了</h1>
<h2>${ex }</h2>
</body>
</html>
3.2 公共處理異常的類@ControllerAdvice
在之前的實驗中,我們只在一個類中處理一個數學異常,萬一這個類中還有其他異常,或者其他類中有異常。按照上面實驗的寫法過於繁瑣,所以有一個公共處理異常的類@ControllerAdvice可以減少繁瑣。
package com.test.controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
/*
* 1.集中處理所有異常加入到IOC容器中
* @ControllerAdvice專門處理異常的類
*/
@ControllerAdvice
public class MyJiZhongException {
@ExceptionHandler(value={ArithmeticException.class})
public ModelAndView handleException01(Exception exception){
System.out.println("handleException01...");
ModelAndView modelAndView=new ModelAndView("myerror");
modelAndView.addObject("ex", exception);
//視圖解析器拼串
return modelAndView;
}
@ExceptionHandler(value={NullPointerException.class})
public ModelAndView handleException02(Exception exception){
System.out.println("handleException01...");
ModelAndView modelAndView=new ModelAndView("myerror");
modelAndView.addObject("ex", exception);
//視圖解析器拼串
return modelAndView;
}
}
要注意的是全局異常處理與本類同時存在,本類優先,即使是全局異常處理類的精確度優先於本類。
3.3 ResponseStatusExceptionResolver
3.3.1 概述
在異常及異常父類中找到@ResponseStatus註解,然後使用這個註解的屬性進行處理。
定義一個 @ResponseStatus 註解修飾的異常類。
若在處理器方法中拋出了上述異常:若ExceptionHandlerExceptionResolver 不解析上述異常。由於觸發的異常 UnauthorizedException 帶有@ResponseStatus 註解。因此會被ResponseStatusExceptionResolver 解析到。最後響應HttpStatus.UNAUTHORIZED 代碼給客戶端。HttpStatus.UNAUTHORIZED 代表響應碼401,無權限。 關於其他的響應碼請參考 HttpStatus 枚舉類型源碼。
3.3.2 實驗代碼
1.頁面鏈接
<a href="${ctp }/handle02?username=admin">handle02</a>
2.自定義異常類
package com.test.controller;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(reason="用戶登錄被拒絕",value=HttpStatus.NOT_EXTENDED)
public class UsernameNotFoundException extends RuntimeException{
/**
*
*/
private static final long serialVersionUID = 1L;
}
3.處理器方法
@RequestMapping("/handle02")
public String handle02(@RequestParam("username")String username){
if(!"admin".equals(username))
{
System.out.println("登錄失敗");
throw new UsernameNotFoundException();
}
System.out.println("登錄成功");
return "success";
}
4.使用上面註解進行測試,出現的頁面:
5.將@ResponseStatus註解標註到方法上,執行會出現:
3.4 DefaultHandlerExceptionResolver
3.4.1 概述
對一些特殊的異常進行處理:
- NoSuchRequestHandlingMethodException
- HttpRequestMethodNotSupportedException
- HttpMediaTypeSupportedException
- HttpMediaTypeNotAcceptableException等。
3.4.2 實驗代碼
1.增加頁面鏈接:GET請求
<a href="${ctp }/handle03">handle03</a>
2.增加處理器方法
@RequestMapping(value="/handle03",method=RequestMethod.POST)
public String handle03(){
return "success";
}
3.出現異常錯誤
4.出現異常交給DefaultHandlerExceptionResolver處理(源碼):
@Override
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
if (ex instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
handler);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,
handler);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response,
handler);
}
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request,
response, handler);
}
else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException((ServletRequestBindingException) ex, request, response,
handler);
}
else if (ex instanceof ConversionNotSupportedException) {
return handleConversionNotSupported((ConversionNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch((TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler);
}
else if (ex instanceof MethodArgumentNotValidException) {
return handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestPartException) {
return handleMissingServletRequestPartException((MissingServletRequestPartException) ex, request, response, handler);
}
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler);
}
}
catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
}
return null;
}
3.5 SimpleMappingExceptionResolver
3.5.1 概述
如果希望對所有異常進行統一處理,可以使用SimpleMappingExceptionResolver,它將異常類名映射爲視圖名,即發生異常時使用對應的視圖報告異常。
3.5.2 實驗代碼
1.增加頁面鏈接
<a href="${ctp }/handle04">handle04</a>
2. 增加控制器方法
@RequestMapping("/handle04")
public String handle04(){
System.out.println("handle04");
String str=null;
System.out.println(str.charAt(0));
return "success";
}
3.配置異常解析器:自動將異常存放到request範圍內
<bean id="simpleMappingExceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- exceptionAttribute默認值(通過ModelAndView傳遞給頁面):
exception -> ${requestScope.exception}
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
-->
<property name="exceptionAttribute" value="exception"></property>
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>
3.5.3 源碼分析
@Override
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) {
// Apply HTTP status code for error views, if specified.
// Only apply it if we're processing a top-level request.
Integer statusCode = determineStatusCode(request, viewName);
if (statusCode != null) {
applyStatusCodeIfPossible(request, response, statusCode);
}
return getModelAndView(viewName, ex, request);
}else {
return null;
}
}
/**
* Return a ModelAndView for the given view name and exception.
* <p>The default implementation adds the specified exception attribute.
* Can be overridden in subclasses.
* @param viewName the name of the error view
* @param ex the exception that got thrown during handler execution
* @return the ModelAndView instance
* @see #setExceptionAttribute
*/
protected ModelAndView getModelAndView(String viewName, Exception ex) {
ModelAndView mv = new ModelAndView(viewName);
if (this.exceptionAttribute != null) {
if (logger.isDebugEnabled()) {
logger.debug("Exposing Exception as model attribute '" + this.exceptionAttribute + "'");
}
mv.addObject(this.exceptionAttribute, ex);
}
return mv;
}