在实际的应用开发中,经常会不可避免地遇到各种可预知的、不可预知的异常,此时我们就需要对这些异常处理,以保证程序正常运行。
Spring MVC 提供了一个名为 HandlerExceptionResolver 的异常处理器接口,它可以对控制器方法执行过程中出现的各种异常进行处理。
Srping MVC 为 HandlerExceptionResolver 接口提供了多个不同的实现类,其中最常用的实现类如下。
DefaultHandlerExceptionResolver
ResponseStatusExceptionResolver
ExceptionHandlerExceptionResolver
SimpleMappingExceptionResolver
其中,ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver 和 DefaultHandlerExceptionResolver 是 Spring MVC 的默认异常处理器。
如果程序发生异常,Spring MVC 会按照 ExceptionHandlerExceptionResolver → ResponseStatusExceptionResolver → DefaultHandlerExceptionResolver 的顺序,依次使用这三个异常处理器对异常进行解析,直到完成对异常的解析工作为止。
DefaultHandlerExceptionResolver
DefaultHandlerExceptionResolver 是 HandlerExceptionResolver 接口的常用实现类之一,更是 Spring MVC 提供的默认异常处理器之一,Spring MVC 默认通过它对控制器处理请求时出现的异常进行处理。
DefaultHandlerExceptionResolver 提供了一个 doResolveException() 方法,其返回类型为 ModelAndView。该方法会在控制器方法出现指定异常时,生成一个新的包含了异常信息的 ModelAndView 对象替换控制器方法的 ModelAndView 对象,以达到跳转到指定的错误页面,展示异常信息的目的,其部分源码如下。
@Nullable
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return this.handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException)ex, request, response, handler);
}
if (ex instanceof HttpMediaTypeNotSupportedException) {
return this.handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException)ex, request, response, handler);
}
if (ex instanceof HttpMediaTypeNotAcceptableException) {
return this.handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException)ex, request, response, handler);
}
……
} catch (Exception var6) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", var6);
}
}
return null;
}
从上面的代码可以看出,DefaultHandlerExceptionResolver 的 doResolveException() 方法可以将 Spring MVC 产生的各种异常转换为合适的状态码(code)。通过这些状态码,我们就可以进一步的确定发生异常的原因,以便于找到对应的问题。
异常 | 状态码 | 说明 |
---|---|---|
HttpRequestMethodNotSupportedException | 405(Method Not Allowed) | HTTP 请求方式不支持异常 |
HttpMediaTypeNotSupportedException | 415(Unsupported Media Type | ) HTTP 媒体类型不支持异常 |
HttpMediaTypeNotAcceptableException | 406(Not Acceptable) | HTTP 媒体类型不可接受异常 |
BindException | 400(Bad Request) | 数据绑定异常 |
MissingServletRequestParameterException | 400(Bad Request) | 缺少参数异常 |
ConversionNotSupportedException | 500(Internal Server Error) | 数据类型转换异常 |
TypeMismatchException | 400(Bad Request) | 类型不匹配异常 |
HttpMessageNotReadableException | 400(Bad Request) | HTTP 消息不可读异常 |
HttpMessageNotWritableException | 500(Internal Server Error) | HTTP 消息不可写异常 |
上表中只列举一些常见的异常状态码,至于更多的异常及其状态码映射,请参考 org.springframework.http.HttpStatus。
ResponseStatusExceptionResolver
ResponseStatusExceptionResolver 也是 HandlerExceptionResolver 的实现类之一。与 DefaultHandlerExceptionResolver 一样,ResponseStatusExceptionResolver 也是 Spring MVC 提供的默认异常处理器之一,它被用来解析 @ResponseStatus 注解标注的自定义异常,并把异常的状态信息返回给客户端展示。
@ResponseStatus 注解
@ResponseStatus 注解主要用来标注在自定义的异常类上,示例代码如下。
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "自定义异常")
public class UserNotExistException extends RuntimeException {
}
如果程序运行时发生了这个自定义的异常,Spring MVC 就会通过 ResponseStatusExceptionResolver 对该异常进行解析,并将异常信息展示到错误页上。
@ResponseStatus 注解包含了三个属性,如下表。
属性 | 说明 |
---|---|
code | 设置异常的状态码。code 为 @ResponseStatus 注解 value 属性的别名,与 value 属性完全等价。value 设置异常的状态码。 |
value | 为 @ResponseStatus 注解 code 属性的别名,与 code 属性完全等价。 |
reason | 设置异常的原因或描述。 |
ExceptionHandlerExceptionResolver
ExceptionHandlerExceptionResolver 是 HandlerExceptionResolver 接口的实现类之一,它也是 Spring MVC 提供的默认异常处理器之一。
ExceptionHandlerExceptionResolver 可以在控制器方法出现异常时,调用相应的 @ExceptionHandler 方法(即使用了 @ExceptionHandler 注解的方法)对异常进行处理。
@ExceptionHandler 注解
Spring MVC 允许我们在控制器类(Controller 类)中通过 @ExceptionHandler 注解来定义一个处理异常的方法,以实现对控制器类内发生异常的处理。
@ExceptionHandler 注解中包含了一个 value 属性,我们可以通过该属性来声明一个指定的异常。如果在程序运行过程中,这个 Controller 类中的方法发生了这个指定的异常,那么 ExceptionHandlerExceptionResolver 就会调用这个 @ExceptionHandler 方法对异常进行处理。
package net.biancheng.c.controller;
import net.biancheng.c.exception.UserNotExistException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ExceptionController2 {
//控制器方法
@RequestMapping(value = "/testExceptionHandler")
public String testExceptionHandler() {
//发生 ArithmeticException 异常
System.out.println(10 / 0);
return "success";
}
//使用 @ExceptionHandler 注解定义一个异常处理方法
@ExceptionHandler(ArithmeticException.class)
public String handleException(ArithmeticException exception, Model model) {
//将异常信息通过 Model 放到 request 域中,以方便在页面中展示异常信息
model.addAttribute("ex", exception);
//跳转到错误页
return "error";
}
}
@ExceptionHandler 方法的优先级
如果我们在同一个控制器类内使用 @ExceptionHandler 注解定义了多个异常处理的方法,那么我们就需要注意下 @ExceptionHandler 方法的优先级问题。
handleException 声明的异常为 Exception,handleException 声明的异常为 RuntimeException,且 Exception 是 RuntimeException 的父类。若此时控制器方法发生了 ArithmeticException 异常,那么 ExceptionHandlerExceptionResolver 会根据继承关系,调用继承深度最浅的异常处理方法(即 handleException2 方法),对异常进行处理。
注意,定义在某个控制器类中的 @ExceptionHandler 方法只在当前的控制器中有效,它只能处理其所在控制器类中发生的异常。
全局异常处理
我们还可以将 @ExceptionHandler 方法定义在一个使用了 @ControllerAdvice 注解的类中。使用 @ControllerAdvice 注解的类可以包含多个不同的带有 @ExceptionHandler 注解的方法,这些方法可以应用应用程序中所有带有 @RequestMapping 注解的控制器方法中,实现全局异常处理。
package net.biancheng.c.controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class ExceptionControllerAdvice {
@ExceptionHandler
public String exceptionAdvice(Exception exception, Model model) {
System.out.println("ExceptionControllerAdvice>>>>>>>>>>>>>>>>>>>");
model.addAttribute("ex", exception);
return "error-2";
}
}
SimpleMappingExceptionResolver
Spring 提供了一个自定义的异常处理器:org.springframework.web.servlet.handler.SimpleMappingExceptionResolver ,也能够实现对所有异常的统一处理,具体步骤如下。
- 默认情况下,Spring MVC 的上下文中并没有装配 SimpleMappingExceptionResolver 的实例,因此我们在使用它进行统一处理异常前,需要在 Spring MVC 配置文件中配置异常类和 View 的对应关系,具体代码如下:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--properties的键表示处理器方法执行过程中出现的异常
properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面-->
<prop key="net.biancheng.c.exception.UserNotExistException">error-3</prop>
</props>
</property>
<!-- exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享-->
<property name="exceptionAttribute" value="ex"></property>
</bean>
- 在 webapp/WEB-INF/templates 目录下,新建一个名为 error-3 的错误页,代码如下。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
error-3 出错了!
<h4 th:text="${ex}"></h4>
</body>
</html>