Spring MVC提供了几种异常处理的方法:
- 对于每个异常类的处理
- 对于每个@Controller类的异常处理
- 对于所有@Controller类的全局异常处理
基于@ResponseStatus的异常处理
通常,处理Web请求时引发的任何未处理的异常都会导致服务器返回HTTP 500响应。 但是,您自己编写的任何自定义异常都可以使用@ResponseStatus注释进行注释。 当一个带注释的异常从控制器方法中抛出,并且没有在其他地方处理时,它会自动返回使用指定的状态码的HTTP响应。下面是一个示例,当找不到指定的订单时,抛出订单未找到的异常。
//定义一个@ResponseStatus注释的异常类
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404
public class OrderNotFoundException extends RuntimeException {
// ...
}
//定义一个订单Controller类
@Controller
public class OrderController{
@RequestMapping(value="/orders/{id}", method=GET)
public String showOrder(@PathVariable("id") long id, Model model) {
Order order = orderRepository.findOrderById(id);
if (order == null)
throw new OrderNotFoundException(id);
model.addAttribute(order);
return "orderDetail";
}
}
上面的实例中,当访问一个不存在的订单时,将返回一个HTTP 404错误页面。
基于Controller的异常处理
通过在Controller类中单独添加一个@ExceptionHandler注释的方法可以处理该Controller类中@RequestMapping注解的方法处理时抛出的异常。@ExceptionHandler注释的方法可以:
- 不带@ResponseStatus注释的异常(比如系统自带的异常类)
- 将用户引导到专门的错误页面
- 创建一个总体的自定义错误响应
@Controller
public class ExceptionHandlingController {
// @RequestMapping methods
...
// Exception handling methods
// 将指定异常转换为对应的HTTP状态码
@ResponseStatus(value=HttpStatus.CONFLICT,reason="Data integrity violation") // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public void conflict() {
// Nothing to do
}
// 指定用于显示指定异常的view名称
@ExceptionHandler({SQLException.class,DataAccessException.class})
public String databaseError() {
// Nothing to do. Returns the logical view name of an error page, passed
// to the view-resolver(s) in usual way.
// Note that the exception is NOT available to this view (it is not added
// to the model) but see "Extending ExceptionHandlerExceptionResolver"
// below.
return "databaseError";
}
// 总体控制-将以上具体异常类之外的异常引导到对应错误页. Or
// consider subclassing ExceptionHandlerExceptionResolver (see below).
@ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
上面的实例中,如果@Controller类的@RequestMapping方法执行时抛出了异常,则会触发@Controller类中异常处理方法。
如果是DataIntegrityViolationException异常,则返回409错误;
如果是SQLException或DataAccessException,则跳转到databaseError页面;
否则的话,将跳转到error页面。
小技巧
由于在跳转到的错误页中,普通用户是不希望看到具体的JAVA错误详情和代码调用堆栈的,但是对于程序开发者来说这些信息却非常有用,因此,可以通过将这些信息转化为页面的注释不失为一个两全其美的解决方案。
<!--JSP-->
<h1>Error Page</h1>
<p>Application has encountered an error. Please contact support on ...</p>
<!--
Failed URL: ${url}
Exception: ${exception.message}
<c:forEach items="${exception.stackTrace}" var="ste"> ${ste}
</c:forEach>
-->
如果开发者要了解异常详情的话,可以通过浏览HTML源码来查看。
全局异常处理-使用@ControllerAdvice
虽然上面介绍的两种异常处理方式都是很好的工作,但是使用@ControllerAdvice的全局异常处理方式更加优雅,它可以处理整个应用程序中所有@Controller类的异常,从而将异常处理逻辑从业务代码中分离出来。
所有使用@ControllerAdvice注解的类都可以变成Controller切面,该类中@ExceptionHandler, @InitBinder, @ModelAttribute注解方法可以在所有的@Controller类中共享。
- @ExceptionHandler注解的方法用于异常处理。
- @InitBinder注解的方法用于handler方法参数从web请求到java bean的数据绑定。
- @ModelAttribute注解的方法用于增强Model。
下面是全局异常示例:
@ControllerAdvice
class GlobalDefaultExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
@ResponseStatus(HttpStatus.CONFLICT) // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
// If the exception is annotated with @ResponseStatus rethrow it and let
// the framework handle it - like the OrderNotFoundException example
// at the start of this post.
// AnnotationUtils is a Spring Framework utility class.
if (AnnotationUtils.findAnnotation
(e.getClass(), ResponseStatus.class) != null)
throw e;
// Otherwise setup and send the user to a default error-view.
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}
HandlerExceptionResolver
DispatcherServlet的应用程序上下文中任何实现HandlerExceptionResolver的类都能拦截和处理Spring MVC系统抛出的并且@Controller类未处理的异常。
SpringMVC默认创建三个异常解析器。
- ExceptionHandlerExceptionResolver匹配@Controller和@ControllerAdvice注解的handler类中的@ExceptionHandler方法。
- ResponseStatusExceptionResolver查找@ResponseStatus注解的未捕获异常 。
- DefaultHandlerExceptionResolver将标准的Spring异常转化为HTTP状态码。
SimpleMappingExceptionResolver
Spring很早就提供了一个简单但方便的HandlerExceptionResolver实现,你可能已经发现它已经被你的应用程序使用过了 - SimpleMappingExceptionResolver。它提供一下功能:
- 建立异常类和view名称的映射。
- 为其他地方未处理异常指定一个默认的错误页。
- 记录日志消息。
- 通过给异常属性设置名称将其添加到Model中,这样就能在view中使用。
@Configuration
@EnableWebMvc // Optionally setup Spring MVC defaults (if you aren't using
// Spring Boot & haven't specified @EnableWebMvc elsewhere)
public class MvcConfiguration extends WebMvcConfigurerAdapter {
@Bean(name="simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver
createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r =
new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError");
mappings.setProperty("InvalidCreditCardException", "creditCardError");
r.setExceptionMappings(mappings); // None by default
r.setDefaultErrorView("error"); // No default
r.setExceptionAttribute("ex"); // Default is "exception"
r.setWarnLogCategory("example.MvcLogger"); // No default
return r;
}
...
}