如何使用SpringMvc处理Rest异常
Posted Java高级部落
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用SpringMvc处理Rest异常相关的知识,希望对你有一定的参考价值。
若你的项目中已经在使用spring,然后你又需要提供rest接口,那么springmvc是一个不错的选择。
不过,由于rest并不包含用户界面(rest更倾向于用纯文本表达),而springmvc则老是想着“生成用户界面、生成用户界面”,所以,想要用springmvc来更restful地表述错误或问题,并没有那么容易。
那么我们应该如何用springmvc产出更符合restful的错误信息呢?
若有异常发生,rest建议我们通过设置HTTP状态码的方式大体地区分失败的原因。大多数rest API设计者认为,尽可能地重用HTTP规范定义的状态码是最好的,因为许许多多的http客户端都能理解这些错误情况的绝大多数,并且,“重用”这件事鼓励行为的一致性,这对开发有好处。
然而,原生HTTP规范只有24种状态码用来描述错误情况:其中18种4xx状态码描述客户端错误,6种5xx状态码描述服务端错误(也有其他规范定义了更多的状态码,比如WebDav,但它们流传不广)。这就有一个问题:这24种状态码太过泛化——它们有可能并不能描述一个特定问题的所有细节。
最好给你的rest API使用者们尽量多的信息,以便他们诊断和修复问题。你的rest API越容易使用,他们就越可能用你的服务(译注:这年头,连要服务别人都竞争激烈) 。
既然状态码很可能不够用,那么当最终用户遭遇错误情况时,我们可以提供什么其他东西来协助他们呢?显然可以提供可读的错误信息,方便开发者查看。但我们其实还可以增加更多信息,以提供一个又直观又很有帮助的错误描述。
Apigee公司(Apigee.com)有人在博客上整理了一篇值得一看的关于如何表述restful错误情况文章(http://blog.apigee.com/detail/restful_api_design_what_about_errors),还有一些很好的视频(http://www.youtube.com/watch?v=QpAhXa12xvU)。我们要做类似的事情。
下面的例子是我觉得比较好的rest错误情况表述(例子是json格式的。xml的类似):
{
"status": 404,
"code": 40483,
"message": "Oops! It looks like that file does not exist.",
"developerMessage": "File resource for path /uploads/foobar.txt does not exist. Please wait 10 minutes until the upload batch completes before checking again.",
"moreInfo": "http://www.mycompany.com/errors/40483"
}
后面我将详述这些属性。
状态/status
“状态”属性是整型的,而且跟http状态码值相同。这是一个便捷通道:把状态码在响应体里也放一份,那么所有rest客户端处理错误时,只需要看响应体这一个地方就可以完整地理解错误:错误自表述了,不需要去检查响应头或其他地方才能明白了。
探讨
首先说思想,响应对象也是个对象,该用就用什么属性就用什么属性,该用响应头就用响应头,没必要把响应头视为(比响应体)低人一等。甚至理论上严格来说,响应体放的是uri指向的资源,响应头放的是描述资源和本次请求--响应的元信息,而错误情况的描述文本恰好属于“本次请求--响应的元信息”或“资源的元数据”,所以把错误情况放在响应体里是错误的,应该放在响应头里。
再看方案,其实并不能解决问题。复制一个状态码放在响应体里不是不可以,但是“让客户端不需要去响应头里看状态码”是无法达成的。因为有些错误很有可能不是服务端业务代码产生的,很有可能是诸如nginx、tomcat、springmvc、struts之类的框架、中间件产生的,甚至还有可能是在服务端-客户端之间网络的中间节点(比如dns、代理节点、网关blabla)就挂了,服务端根本就没收到请求。服务端无法保证这些节点发生错误也会遵照作者上述的做法,所以客户端就无论如何都得考虑处理这些情况,而处理这些情况就必须从响应头里获取状态码。而既然都已经通过响应头获取状态码了,又何必再去响应体里获取一遍?多此一举。
我认为在使用http客户端时,处理响应的流程如下:
要捕获住所使用的http客户端组件声明的所有异常。此时请求可能都还没有发出去,问题的原因一般是程序员使用有误、参数有误、此http客户端组件有bug、网络问题。遇到这种情况,应将组件特有异常转译成自定义的异常抛出。
调用http客户端组件发起请求,得到响应对象,通常先检查是否为null。若为null,原因一般是此http客户端组件设计得不好,没有很好地定义自己的行为结果,令使用者无法得知当前状态。遇到这种情况只能当“未知异常”抛出(好的http客户端不会来到这里,要么触发1要么触发3)。
若http客户端组件的响应对象自定义了类似于“查看本次请求--响应状态”这样的接口,可以考虑调用它来判断。这时要具体情况具体分析,该重试重试,该抛异常抛异常。
查看响应对象的http状态码值。对于那些有可能是中间结点返回的错误响应(常见的包括401、403、404、405、406、408、409、429、500、502、503、504)要特别注意,它们的响应体未必符合http接口文档里声明的格式,所以需要检查响应头(比如检查Content-Type头是否符合期望),然后才是尝试解析。尝试解析时也需要捕获住所使用解析组件的所有异常(比如用jackson解析json响应体,需要捕获所有可能会被抛出来的jackson的异常)。
中间节点不会使用的那些状态码,是服务端主动触发的,就直接按http接口文档约定的异常情况处理即可。
解析得到符合http接口约定的异常响应体后,就可以开展业务处理流程了。这时也需要注意,更严谨一些的话,也需要捕获住一些特定的异常,比如空指针、NumberFormatException等。这么做是为了避免接口做了不兼容修改而接口文档没有及时更新导致的错误。
错误码/code
一个“错误码”属性通常用来表示错误场景下的一个特定信息。
由于通用的HTTP错误码过少导致了一定的局限性,所以推荐使用自定义错误码,可以用来表达更多更丰富的特定的失败原因。再次强调,API客户端获得的信息越多越好。
在上面的例子中,错误码属性的值是40483。通用的那个“状态码”(404)表明没找到该资源,然后有一个应用特有的错误码40483,来表明该资源不光是没找到,而且还表明了是因为尚未被上传到服务器。
探讨
作者的意思应该是可以从“存在性”维度来区别诸如“未存在过”、“曾经拥有现已搬走”、“曾经拥有现不知所踪”、“暂时不在稍后回来”等不同的细分情况。若是从业务维度来细分错误码,我认为是可行的,但这里是从一个非业务维度细分,值得商榷(作者至少应该拿出更好的例子来)。
由于rest/http是按无状态设计的,这里的“无状态”是指不考虑历史取值、值的变化情况,对“曾经”和“未曾”一视同仁,更看重结果和未来。
所以在“存在性”维度,以结果和未来导向的细分情况如下:
1,资源不会再出现在当前位置(uri)
1.1,资源当前位置已知:即已知的永久迁移。使用301状态码。
1.2,资源当前位置未知:类似于死亡。使用410状态码。
2,资源可能再出现在当前位置(uri)
2.1,资源当前位置已知:即已知的临时迁移。使用302状态码。
2.2,资源当前位置未知:由于无状态不考虑历史变迁因素,两种子情况一视同仁,都使用404状态码。
2.2.1,资源曾经存在:即失踪。这里仅罗列一下细分情况。
2.2.2,资源未曾出现过:类似于未出生。这里仅罗列一下细分情况。
这里“上传文件”的例子看起来有点太刻意了,但这里关键是说你的API使用自定义的错误码,可以表达更丰富的错误信息。
提示:若你对某一特殊错误没有自定义错误码,那么可以让错误码属性的值=状态码的值。这样确保错误码永远会有值,客户端不需要检查它是否为null。这对API使用者更容易和优雅,能提高接受度。
友好提示/message
“友好提示”属性是人类可读的错误信息,可以直接显示给应用的最终用户(非开发人员)看。所以它应该是友好而且容易理解的,是描述错误为什么发生的简明摘要。它不应带有技术信息,技术信息应放在“调试信息”属性(见下文)。
这样做有什么好处?
若你的rest API使用者希望把消息展示给最终用户,他们就可以这么做了。这样他们就可以很快而且不用做太多工作地写出用户界面来支持他们自己的最终用户。让API使用者在使用时节省更多时间的事情,做得越多越好。
调试信息/developerMessage
“调试信息”属性可以用来放与技术有关的信息,对调用你rest API的开发者很有用。你可以把异常信息、堆栈或任何你觉得对使用者有帮助的信息放在里面。
详情/moreInfo
“详情”属性指定一个url,可以展示给看到错误信息的人,他们可以点击或把它复制粘贴到浏览器里。url指向的目标网页应该有完整的错误详情以及解决方案,帮助他们解决问题。
这可能是最重要的属性,因为你可以在目标网页上更好地提供信息。你可以提供指向支持部门的链接,可以做一个“在线求助”对话框,或你觉得有帮助的随便什么东西。展现一下程序猿满满的爱心吧,他们就会继续用你的API了。
推荐阅读
▼
▼
以上是关于如何使用SpringMvc处理Rest异常的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Spring 异常处理 POST REST 更正错误状态代码
如何使用 power mock 对 Spring Boot Rest 控制器和异常处理程序进行单元测试
ControllerAdvice 的异常处理程序在使用 Spring Boot 的 Rest API 获取请求中不起作用。如何解决?