文章目錄
一、異常解析的源碼流程
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>
通過配置的異常處理,出現空指針異常時,在回顯頁面打印出了信息。