Exception Handling in Spring MVC翻译

原文地址:https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc#recent-updates

Exception Handing in Spring MVC

Spring Boot 允许Spring项目使用很少的配置就可以搭建起来。
Spring MVC没有提供开箱即用的error page。通常下,通过SimpleMappingExceptionResolver来提供error page。
然而,Spring Boot提供了错误处理页面。
在启动的时候,Spring Boot尝试寻找路径/error的映射。为了方便,一个以/error结尾的URL映射到同名的逻辑视图。在demo应用中,这个视图对应为thymeleaf模板的error.html。(如果是jsp,则映射为error.jsp)。实际的映射取决于ViewResolver的设置。
如果没有视图解析能够映射到/error,Spring Boot定义了自己的默认错误页面: “Whitelabel Error Page”(页面的内容很少,只有HTTP状态信息以及错误详情,比如没有被捕获的exception信息)。在同一个应用中,如果把error.html重命名为其他,例如error2.html,那么将会发现默认错误页会生效。
如果你正在进行Restful请求(HTTP请求设置了非html的返回数据格式),Spring Boot将会把相同的错误信息采用json格式返回。

$> curl -H "Accept: application/json" http://localhost:8080/no-such-page

{"timestamp":"2018-04-11T05:56:03.845+0000","status":404,"error":"Not Found","message":"No message available","path":"/no-such-page"}

Spring Boot也为容器定义了一个默认错误页,提供了和web.xml定义错误页相同的功能(但是两者的实现方式不同)。抛到Spring MVC框架之外的Exception,比如servlet filter中抛出异常,依旧会通过Spring Boot的默认错误页提供信息。
下面的内容和Spring Boot无关。
REST开发者可以选择越过一部分内容,直接阅读REST error相关的信息。然而,推荐阅读整个文章,无论使用REST还是其他方式。

使用HTTP 状态码

通常情况下,处理web请求时产生的未捕获异常,都会导致server返回HTTP 500的错误。然而,任何自定义的exception都可以通过注解@ResponseStatus(支持所有的HTTP状态码)注释。当被注释的exception在controller中抛出,exception会自动解析成配置的状态码返回。
例如,下面的exception。

@ResponseStatus(value=HttpStatus.NOT_FOUND, reason=”No such Order”)
// 404 public class OrderNotFoundException extends RuntimeException {
// … } 抛出异常的controller: @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";  }

如果是一个找不到的order id,那么将会返回HTTP 404状态码。

基于Controller的Exception处理

使用注解 @ExceptionHandler

在同一个controller中,可以为所有的请求处理方法定义指定的异常处理方式。
这些异常处理方式包括:

  • 1 处理没有使用@ResponseStatus注释的exception
  • 2 重定向到指定的error视图
  • 3 构建自定义error返回信息

    例子如下:

@Controller public class ExceptionHandlingController {

// @RequestHandler methods …
// Exception handling methods
// Convert a predefined exception to an HTTP Status code @ResponseStatus(value=HttpStatus.CONFLICT,
reason=”Data integrity violation”) // 409 @ExceptionHandler(DataIntegrityViolationException.class) public void
conflict() {
// Nothing to do }
// Specify name of a specific view that will be used to display the error:
@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”; }

// Total control - setup a model and return the view name yourself.
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;   } }

在这些方法中,可以做一些额外的处理,最常用的是记录异常日志。
处理方法可以定义接收多种参数,比如HttpServletRequest, HttpServletResponse, HttpSession and/or Principle.
注意:@ExceptionHandler注释的方法不能使用Model作为入参出参。可以使用ModelAndVIew,如上面的handleError()方法。

Exception和视图

在model中添加异常信息需要谨慎,用户并不希望在页面看见异常详情和错误堆栈。然而,将异常信息作为描述信息提供给支持人员是很有帮助的。如果使用jsp,可以采用如下的方式来输出异常信息和错误堆栈。

Error Page

Application has encountered an error. Please
contact support on …


全局异常处理

使用@ControllerAdvice

controller advice允许用户使用相同的exception处理技术,但是将处理方式运用到全局,而不是单独的controller。可以把它想象成一个注解驱动的切面。
任何类都可以使用@ControllerAdvice注释成advice,但是只支持三种方法:

  • 配置了@ExceptionHandler注解的异常处理方法
  • 配置了@ModelAttribute的Model加强方法(为Model添加额外的信息)。注意,这些添加的属性在exception处理视图的无法使用。
  • 配置了@InitBinder的Binder初始化方法(用来配置格式化处理)

可以在https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-controller 获得更多的关于@ControllerAdvice方法。
之前定义的所有exception处理器都可以通过controller advice类的方式实现,而且这些处理方式应用到全局。下面是一个例子:

@ControllerAdvice class GlobalControllerExceptionHandler {
@ResponseStatus(HttpStatus.CONFLICT) // 409
@ExceptionHandler(DataIntegrityViolationException.class) public void
handleConflict() { // Nothing to do } }

如果用户想为所有的exception设定默认处理器,可以通过如下方式:

@ControllerAdvice class GlobalDefaultExceptionHandler { public static
final String DEFAULT_ERROR_VIEW = “error”;

@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应用的context中,任何实现了HandlerExceptionResolver的Spring bean,将会用来处理MVC系统抛出的,或者Controller没有捕获的异常。接口如下:

public interface HandlerExceptionResolver { ModelAndView
resolveException(HttpServletRequest request, HttpServletResponse
response, Object handler, Exception ex); }

参数handler指向产生异常的controller。(注意@Controller注释的实例只是Sping MVC handler的一种。比如,HttpInvokerExporter和WebFlow Executor 也是handler的一种)。
MVC默认会创建三个resolver。他们分别的作用为:

  • ExceptionHandlerExceptionResolver。实现handler和Controller Advice中的@ExceptionHandler注解功能。
  • ResponseStatusExceptionResolver。实现未捕获异常的@ResponseStatus注解功能。
  • DefaultHandlerExceptionResolver。转换Sring的标准exception,并转成HTTP状态码。
  • 这些resolver按照列表的顺序执行。在Spring内部,bean HandlerExceptionResolverComposite来操作。
  • 注意这些方法中没有包含Modal。这也就是为什么@ExceptionHandler不能注入modal。
    用户可以实现自定义的HandlerExceptionResolver,来构建自定义的exception处理系统。Handler需要实现Spring的Ordered接口,从而可以定义handler的执行顺序。

SimpleMappingExceptionResolver

Spring为HandlerExceptionResolver提供了便利的默认实现:SimpleMappingExceptionResolver,用户可以在应用中直接使用。resolver提供了以下的选项:
exception类和视图名的映射:仅仅需要指定类名,不需要包路径。
为没有定义handler的exception,制定了默认错误页。
记录错误日志信息(默认没有开启)。
设置exception的哪些属性添加到Model,这些属性可以在view中使用。默认属性名为exception。设置值为null可以让此功能不起作用。记住,@ExceptionHandler方法返回的view没有包含exception,但是在SimplemappingExceptionResolver定义的view可以访问exception信息。
下面是典型java配置:

@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; }
… }

或者通过xml配置:

<bean id="simpleMappingExceptionResolver" class=
"org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<map>
<entry key="DatabaseException" value="databaseError"/>
<entry key="InvalidCreditCardException" value="creditCardError"/>
</map>
</property>
<!-- See note below on how this interacts with Spring Boot -->
<property name="defaultErrorView" value="error"/>
<property name="exceptionAttribute" value="ex"/>
<!-- Name of logger to use to log exceptions. Unset by default,
so logging is disabled unless you set a value. -->
<property name="warnLogCategory" value="example.MvcLogger"/>
</bean>

属性defaultErrorView确定了未捕获异常对应的error page。(大部分应用服务器默认显示java异常堆栈信息,这些信息不应该暴露给用户)。Spring Boot提供了white-label错误页。

扩展SimpleMappingExceptionResolver

扩展SimpleMappingExceptionResolver的理由有如下几点:

  • 可以直接使用构造器设置属性。比如通过构造器启动exception日志和设置logger。
  • 通过复写buildLogMessage改变默认日志信息。默认实现总会返回文本:Handler execution resulted in exception
  • 通过复写doResolveException为error view添加额外的可用信息。

例如:

public class MyMappingExceptionResolver extends SimpleMappingExceptionResolver {
public MyMappingExceptionResolver() {
// Enable logging by providing the name of the logger to use
setWarnLogCategory(MyMappingExceptionResolver.class.getName());
}

@Override
public String buildLogMessage(Exception e, HttpServletRequest req) {
return "MVC exception: " + e.getLocalizedMessage();
}
@Override
protected ModelAndView doResolveException(HttpServletRequest req,
HttpServletResponse resp, Object handler, Exception ex) {
// Call super method to get the ModelAndView
ModelAndView mav = super.doResolveException(req, resp, handler, ex);
// Make the full URL available to the view - note ModelAndView uses
// addObject() but Model uses addAttribute(). They work the same.
mav.addObject("url", request.getRequestURL());
return mav;
}
}

扩展ExceptionHandlerExceptionResolver

用户也可以扩展ExceptionHandlerExceptionResolver,用相同的方式复写doResolveHandlerMethodException方法。
为了确认Resolver被使用,需要设置继承的order属性值小于MAX_INT,从而resolver会在默认的ExceptionHandlerExceptionResolver运行之前运行。

Error和 REST

REST风格的GET请求也会产生异常,并且在之前我们展示了怎样返回标准的HTTP 状态码。然而,怎样返回错误信息呢?这很简单。首先定义错误信息class:

public class ErrorInfo {
public final String url;
public final String ex;

public ErrorInfo(String url, Exception ex) {
this.url = url;
this.ex = ex.getLocalizedMessage();
}
}

然后我们可以通过@ResponseBody返回错误信息:

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MyBadDataException.class) @ResponseBody ErrorInfo
handleBadRequest(HttpServletRequest req, Exception ex) { return new
ErrorInfo(req.getRequestURL(), ex); }

接下来?

通常,Spring喜欢为你提供各种选择,因此用户需要做什么?这里有些经验。不管用户偏爱XML配置或者注解配置,这些都适用。

  • 为自己写的exception添加@ResponseStatus
  • 对于其他的exception,在@ControllerAdvice的@ExceptionHandler方法中处理,或者使用SimpleMappingExceptionResolver的实例。可能用户已经在应用中配置了SimpleMappingExceptionResolver,这时在@ControllerAdvice中添加处理是最简单的方式。
  • 在controller中添加@ExceptionHandler从而实现特别的处理
  • 警告:不要在一个应用中混合使用多种方式。如果一个异常在多个地方处理,可能得不到你要的结果。Controller中的@ExceptionHandler会在@ControllerAdvice中定义的方法之前执行。这和ControllerAdvice定义的order无关。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值