文章目录
一、异常解析的源码流程
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>
通过配置的异常处理,出现空指针异常时,在回显页面打印出了信息。