SpringMVC框架 |SpringMVC的異常處理機制


一、異常解析的源碼流程

SpringMVC通過HandlerExceptionResolver處理程序的異常,包括Handler映射、數據綁定,以及目標方法執行時發生的異常。

SpringMVC默認使用下面三個ExceptionResolver:

HandlerExceptionResolver=
	AnnotationMethodHandlerExceptionResolver,
	ResponseStatusExceptionResolver,
	DefaultHandlerExceptionResolver

但是自從SpringMVC中配置了<mvc:annotation-driven></mvc:annotation-driven>以後,AnnotationMethodHandlerExceptionResolver被替換爲ExceptionHandlerExceptionResolver。所以SpringMVC中默認的就變成了下面三個異常處理器:

  • ①ExceptionHandlerExceptionResolver:處理@ExceptionHandler註解
  • ②ResponseStatusExceptionResolver:處理@ResponseStatus註解
  • ③DefaultHandlerExceptionResolver:判斷 是否是SpringMVC自帶的異常

頁面渲染之前,有異常會先渲染異常

  • 如果上面三個異常解析器都無法處理,會向上拋給tomcat
  • 處理異常內部的默認工作流程:所有異常解析器依次嘗試解析,解析完成進行後續操作,解析失敗,下一個解析器繼續嘗試解析。
源碼:
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);//如果三個異常解析器都無法處理,會向上拋給tomcat。
				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);
		}
	}
	...
}

二、@ExceptionHandler返回一個自定義錯誤頁面

@ExceptionHandler標註在方法上,告訴SpringMVC這個方法專門處理這個類發生的異常。

  • 方法上寫一個Exception用來接收發生的異常。
  • 要攜帶異常信息不能給參數位置寫Model,正確的做法是返回ModelAndView。
  • 如果有多個@ExceptionHandler都能處理這個異常,精確優先
	@ExceptionHandler(value = { ArithmeticException.class, NullPointerException.class }) // 告訴SpringMVC,這個方法專門處理這個類發送的所有異常
	public ModelAndView handleException01(Exception exception) {
		System.out.println("handleException01..." + exception);
		ModelAndView view = new ModelAndView("myerror");
		view.addObject("ex", exception);	
		return view;
	}

前端頁面直接接受隱含模型

myerror.jsp頁面
<body>
	<h1>出錯啦!</h1>
	<h2>出錯信息:${ex }</h2>
</body>

當出現除0算數異常時,提示如下:
在這裏插入圖片描述

統一異常管理

上面的測試是將@ExceptionHandler放在了處理器中,實際上更好的方式是將@ExceptionHandler放在一個單獨的類中,進行全局異常處理

  • 統一異常管理類需要通過@ControllerAdvice註解加入IoC容器中。
  • 全局異常處理與本類異常處理同時存在,本類優先
@ControllerAdvice
public class MyException {
	// 處理空指針異常
	@ExceptionHandler(value = { NullPointerException.class })
	public ModelAndView handleException01(Exception exception) {
		System.out.println("全局的handleException01..." + exception);
		ModelAndView view = new ModelAndView("myerror");
		view.addObject("ex", exception);
		return view;
	}

	// 處理算數異常
	@ExceptionHandler(value = { ArithmeticException.class })
	public ModelAndView handleException02(Exception exception) {
		System.out.println("全局的handleException02..." + exception);
		ModelAndView view = new ModelAndView("myerror");
		view.addObject("ex", exception);
		return view;
	}
}

三、@ResponseStatus返回一個服務器錯誤頁面

@ResponseStatus標註在自定義異常上.

@ResponseStatus(reason = "用戶被拒絕登錄", value = HttpStatus.NOT_ACCEPTABLE)
public class UsernameNotFoundException extends RuntimeException {

	private static final long serialVersionUID = 1L;
}

處理器中,拋出自定義UsernameNotFoundException 異常。

	@RequestMapping("/handle02")
	public String handle02(@RequestParam("username") String username) {
		if (!"admin".equals(username)) {
			System.out.println("登錄失敗");
			throw new UsernameNotFoundException();
		}
		System.out.println("登錄成功");
		return "success";
	}

發送username=admin02的請求:

<a href="${ctp }/handle02?username=admin02">handl01</a>

返回到服務器錯誤頁面:
在這裏插入圖片描述

四、DefaultHandlerExceptionResolver默認異常

DefaultHandlerExceptionResolver判斷是否是SpringMVC自帶的異常。

例如定義如下的POST處理請求,通過GET進行訪問,會出錯。

	@RequestMapping(value = "/handle03", method = RequestMethod.POST)
	public String handle03() {
		return "success";
	}

SpringMVC自己的異常,如HttpRequestMethodNotSupportedException。若沒有處理,會進入到服務器錯誤頁面。
在這裏插入圖片描述

默認的異常

源碼:
public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
	...
	@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 MissingPathVariableException) {
				return handleMissingPathVariable((MissingPathVariableException) 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) {
			if (logger.isWarnEnabled()) {
				logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
			}
		}
		return null;
	}
	...
}

五、基於配置的異常處理

SimpleMappingExceptionResolver:通過配置的方式完成異常處理。
在這裏插入圖片描述

  • exceptionMappings屬性:配置不同的異常去不同的頁面。
  • key:異常全類名。
  • value:表示要去的視圖頁面。
  • exceptionAttribute屬性:指定錯誤信息取出時使用的key,默認爲exception;可以通過這個key在前端獲得異常信息。
	<bean
		class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
		<property name="exceptionMappings">
			<props>
				<prop key="java.lang.NullPointerException">myerror</prop>
			</props>
		</property>
		<!-- 指定錯誤信息取出時使用的key,默認爲exception -->
		<property name="exceptionAttribute" value="ex"></property>
	</bean>

前端顯示頁面

myerror.jsp
<body>
	<h1>出錯啦!</h1>
	<h2>出錯信息:${ex }</h2>
</body>

通過配置的異常處理,出現空指針異常時,在回顯頁面打印出了信息。
在這裏插入圖片描述

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