在本系列文章的第一部分里,我们讨论了异常发生时,该返回给 REST API 调用者的异常表示(格式)的最佳实践。
在本文(第二部分)中,我们将展示如何在使用 Spring MVC 编写的 REST API 中产生那些异常表述信息。
Spring 异常处理
Spring MVC 有两个主要方式来处理在调用 MVC 控制器(译注:Controller,下文统一为控制器)时抛出的异常:HandlerExceptionResolver 和 @ExceptionHandler 注解。
HandlerExceptionResolvers
对于用一种统一的方法来处理异常来说非常的理想,框架组件为你带来性能的提升。@ExceptionHandler
注解在你想手动展示一些基于业务逻辑的特定异常时则是个很好的选择。
它们的机制都非常强大:我们的普通代码可以以其含有的所有面向对象的益处,通过类型安全的方式抛出预期的异常来明确指示为何失败。然后我们可以让分解器和/或处理器来执行从异常到 HTTP 的必要翻译工作。你当然知道关注点分离(separation of concerns)的好处。
对于我们的 REST 异常处理而言,我们想要通过最佳实践的方式将所有的异常以一致的方式呈现。由于这个一致性(要求),采用HandlerExceptionResolver
是非常合适的。 我们将呈献一个 REST 友好的 HandlerExceptionResolver
实现,它可在任何 REST API 中使用。
RestExceptionHandler
虽然大多数现有的 HandlerExceptionResolver
实现适用于基于 Spring MVC 的用户界面,但是对于 REST 异常处理它们并不是十分理想。为了实现第一部分中异常表示的格式,我们创建了自己的 HandlerExceptionResolver
实现,称为 RestExceptionHandler
提示:本文讨论的代码,是一个 Spring MVC REST 应用,可通过 Stormpath 在 Github) 上的 spring-mvc-rest-exhandler 源获取。
当你的 Spring MVC REST 控制器抛出异常时,RestExceptionHandler
将会:
- 将异常转换为一个
RestError
实例。RestError
的实现包含第一部分中讨论过的所有 REST 异常表示属性。 - 基于构建的
RestError
在 HTTP 响应中设置合适的 HTTP 响应状态码 - 将
RestError
表示提供给 HTTP 响应体。在默认情况下,我们提供一个 JSON 消息体,正如第一部分中的例子所示。
马上我们将会涉及 RestExceptionHandler
的底层工作细节。现在让我们通过一个示例项目看看 RestExceptionHandler
的实践,这样你就清楚它到底是如何运作的。
示例应用
你可以检出 Github 上的 spring-mvc-rest-exhandler 项目,它包含了主要的 RestExceptionHandler
实现和一个附加的示例应用。
在检出项目之后,你可以通过 Maven 来构建:
(在项目根目录执行):
$ mvn clean install
该命令会构建 RestExceptionHandler
库(一个 .jar 文件)以及一个单独的示例 web 应用(一个 .war 文件)。你可以在示例目录中运行该应用:
$ cd example $ mvn jetty:run
以上命令将在本机启动 Jetty web 服务器,端口为 8080。
端点
应用启动后,你可以访问以下两个 REST 端点:
http://localhost:8080/v1/users/jsmith
http://localhost:8080/v1/users/djones
但这些只是运行正常的示例资源。本文我们真正在意的是:一个异常表示该是什么样子?
/v1/ 路径下的其他任何资源都会展示一个 HTTP 404 Not Found 的异常,并且包含在我们期望的 Rest 异常表示体中。试着访问下面这个 URL:
http://localhost:8080/v1/users/doesNotExist
你将会看到我们漂亮的 REST 异常表示!干净漂亮……
但这是如何运作的?由于所访问资源不存在,异常被抛出,然后某个东西将这个异常转换成了漂亮的 JSON 异常消息。让我们看看这是怎么发生的。
MVC 控制器
示例应用中有两个控制器 —— UserController
和 DefaultController
。
UserController
UserController 的源码表明它是一个很简单的 Spring MVC 控制器。它模拟了一个成功查找两个示例用户的功能并在用户无法被找到时抛出一个自定义(应用指定)的 UnknownResourceException
。
我们期望 UnknownResourceException
通过我们的 RestExceptionHandler
配置(我们很快会涉及到),自动转换 HTTP 404 (Not Found)为一个漂亮的异常表示。
DefaultController
DefaultController 源码表明它是一个基础设施组件的作用。通过 @RequestMapping
, 我们能看到在 Spring 无法找到一个更明确的控制器时,Spring会调用这个默认的控制器。
DefaultController
十分简单:它在任何场景下始终抛出一个 UnknownResourceException
。这对 REST 应用来说是有益的,因为在没有其他端点可以为一个请求服务时,我们总想展示一个相关的异常消息。
我们看到了 MVC 控制器如期的抛出漂亮的类型安全的异常。现在我们来看看RestExceptionHandler
如何将这些异常转换 HTTP 异常消息体以及你可以如何定制它的行为。
RestExceptionHandler 的 Spring 配置
这里有一个基础的 RestExceptionHandler
Spring bean 定义:
<bean id="restExceptionResolver" class="com.stormpath.spring.web.servlet.handler.RestExceptionHandler"> <property name="order" value="100"></property> <property name="errorResolver"> <bean class="com.stormpath.spring.web.servlet.handler.DefaultRestErrorResolver"> <property name="localeResolver" ref="localeResolver"></property> <property name="defaultMoreIn