1.概述
本文将重点介绍如何使用REST API的Spring实现异常处理 。 我们将介绍在Spring 3.2之前可用的较旧的解决方案,然后是对Spring 3.2的新支持。
本文的主要目的是展示如何最好地将应用程序中的异常映射到HTTP状态代码。 哪种状态代码不适合本文中的哪种情况,REST错误表示的语法也不属于本文的范围。
在Spring 3.2之前,在Spring MVC应用程序中处理异常的两种主要方法是: HandlerExceptionResolver和@ExceptionHandler批注。 Spring 3.2引入了新的@ControllerAdvice注释,以解决前两种解决方案的局限性。
所有这些确实有一个共同点–它们很好地处理了关注点分离 :标准应用程序代码可以正常抛出异常以指示某种类型的失败–然后可以通过以下任意方式处理异常。
2.通过控制器级别
定义带有@ExceptionHandler注释的Controller级别方法非常容易:
public class FooController{...@ExceptionHandler({ CustomException1.class, CustomException2.class })public void handleException() {//}
}
一切都很好,但是这种方法确实有一个主要缺点–带@ExceptionHandler注释的方法仅对特定Controller有效 ,而不对整个应用程序全局有效。 当然,这使其不适用于通用异常处理机制。
一个常见的解决方案是让应用程序中的所有Controller都扩展Base Controller类 -但是,对于由于某种原因无法使Controllers扩展为此类的应用程序来说,这可能是个问题。 例如,控制器可能已经从另一个基类扩展了,该基类可能在另一个jar中,或者不能直接修改,或者它们本身也不能直接修改。
接下来,我们将讨论另一种解决异常处理问题的方法-一种全局的方法,不包括对现有工件(如Controllers)的任何更改。
3.通过
为了在REST API中实现统一的异常处理机制 ,我们需要使用HandlerExceptionResolver-这将解决应用程序在运行时抛出的所有异常。 在寻求自定义解析器之前,让我们看一下现有的实现。
3.1。 ExceptionHandlerExceptionResolver
该解析器是在Spring 3.1中引入的,默认情况下已在DispatcherServlet中启用。 这实际上是前面介绍的@ ExceptionHandler机制如何工作的核心组件。
3.2。 DefaultHandlerExceptionResolver
该解析器是在Spring 3.0中引入的,默认情况下已在DispatcherServlet中启用。 它用于将标准Spring异常解析为其对应的HTTP状态代码,即客户端错误– 4xx和服务器错误– 5xx状态代码。 这是它处理的Spring Exception 的完整列表 ,以及如何将它们映射到状态代码。
尽管它确实正确设置了响应的状态码,但此解析器的一个局限性在于它没有对响应的主体设置任何内容。 但是,在REST API的上下文中,状态代码确实不足以向客户端提供信息-响应也必须具有正文,以允许应用程序提供有关失败原因的其他信息。
这可以通过配置View分辨率和通过ModelAndView渲染错误内容来解决,但是解决方案显然不是最优的-这就是为什么Spring 3.2提供了更好的选择的原因-我们将在本文的后半部分讨论。
3.3。 ResponseStatusExceptionResolver
该解析器也在Spring 3.0中引入,默认情况下在DispatcherServlet中启用。 它的主要职责是使用自定义异常上可用的@ResponseStatus批注,并将这些异常映射到HTTP状态代码。
这样的自定义异常可能看起来像:
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public final class ResourceNotFoundException extends RuntimeException {public ResourceNotFoundException() {super();}public ResourceNotFoundException(String message, Throwable cause) {super(message, cause);}public ResourceNotFoundException(String message) {super(message);}public ResourceNotFoundException(Throwable cause) {super(cause);}
}
与DefaultHandlerExceptionResolver一样 ,此解析器在处理响应主体方面也受到限制 -它确实将状态代码映射到响应上,但主体仍为null。
3.4。 SimpleMappingExceptionResolver和AnnotationMethodHandlerExceptionResolver
SimpleMappingExceptionResolver已经存在了很长时间–它来自较早的Spring MVC模型,并且与REST Service无关 。 它用于将异常类名称映射到视图名称。
Spring 3.0中引入了AnnotationMethodHandlerExceptionResolver来通过@ExceptionHandler批注处理异常,但是从Spring 3.2开始, ExceptionHandlerExceptionResolver已弃用该方法。
3.5。 自定义HandlerExceptionResolver
DefaultHandlerExceptionResolver和ResponseStatusExceptionResolver的组合在为Spring RESTful Service提供良好的错误处理机制方面有很长的路要走-但主要的限制-无法控制响应的主体-证明创建新的异常解析器是合理的。
因此,新解析器的一个目标是启用一个信息量更大的响应主体,该主体也应符合客户端请求的表示类型(由Accept标头指定):
@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {@Overrideprotected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {try {if (ex instanceof IllegalArgumentException) {return handleIllegalArgument((IllegalArgumentException) ex, response, handler);}...} catch (Exception handlerException) {logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);}return null;}private ModelAndView handleIllegalArgument(IllegalArgumentException ex, HttpServletResponse response) throws IOException {response.sendError(HttpServletResponse.SC_CONFLICT);String accept = request.getHeader(HttpHeaders.ACCEPT);...return new ModelAndView();}
}
这里需要注意的一个细节是请求本身是可用的,因此应用程序可以考虑客户端发送的Accept标头的值。 例如,如果客户端要求输入application / json,则在出现错误情况时,应用程序仍应返回以application / json编码的响应正文。
另一个重要的实现细节是返回ModelAndView -这是响应的主体,它将允许应用程序对其进行必要的设置。
这种方法是用于Spring REST服务的错误处理的一致且易于配置的机制。 但是它确实有局限性 :它与低级HtttpServletResponse交互,并且适合使用ModelAndView的旧MVC模型-因此仍有改进的空间。
4.通过新的
Spring 3.2通过新的@ControllerAdvice批注支持全局@ExceptionHandler 。 这将启用一种机制,该机制有别于旧的MVC模型,并利用ResponseEntity以及@ExceptionHandler的类型安全性和灵活性:
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {@ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class })protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {String bodyOfResponse = "This should be application specific";return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request);}
}
新的批注允许将之前分散的多个@ExceptionHandler合并到单个全局错误处理组件中 。
实际的机制非常简单,但也非常灵活:
- 它允许完全控制响应的主体以及状态代码
- 它允许将多个异常映射到同一方法,以便一起处理
- 它充分利用了更新的RESTful ResposeEntity响应
5.结论
本教程讨论了几种在Spring中为REST API实现异常处理机制的方法,从较旧的机制开始,一直到对Spring 3.2的新支持。 要在现实世界的REST服务中完整实现这些异常处理机制,请查看github项目 。
翻译自: https://www.javacodegeeks.com/2013/02/exception-handling-for-rest-with-spring-3-2.html