SpringMVC-異常處理

一.概述

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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章