本文通过对RESTful WebService中异常处理的几个关键点如自定义错误码、定制错误消息、自定义异常、全局异常处理进行介绍,与读者分享本人对Spring异常处理和对RESTful API设计的思考和实践。
随着前后端分离,前端工程化,后端微服务化,越来越多的应用都开始倾向于使用 RESTful API 为各种各样的客户端提供服务。设计一套优雅的 API 服务,需要诸多考量,而异常处理往往被忽视,而我认为这恰恰是评判一套 API 设计好坏的很重要的一个衡量因素。经过很多的经验借鉴和思考,最终形成了一套我认为还算合理的异常处理方式,权作抛砖引玉。
本文虽然名为 Spring Boot 异常处理最佳实践,但并不仅限于 Spring Boot 应用,普通的 Spring RESTful Webservice 也都可以采用此实践。对于异常处理,最关键的无非是定义错误码、定制错误消息、自定义异常类几个环节,关于这些点,以及其中可能遇到的问题,笔者在写作此文之前,都是经过了深思熟虑的,也都尽可能在文章中或者在代码中指出来了,供读者参考。
错误码
当我们的 API 提供给调用方使用的时候,除了正确的请求、响应接口说明以及示例,还应该给出一个符合统一规范的错误消息说明和一组错误码说明。可能有人习惯使用数字作为错误码,但理论上讲,无论是接口还是数据库,使用本身无意义的数字作为字段值,都属于非常糟糕的设计。关于这点,有必要解释一下。设想如下场景,我们设计了一张表,其中很多枚举字段,假如都使用数字来存储,比如某个 Task 的执行状态可能有[1-就绪;2-执行中;3-故障恢复中;4-已完成]几种,那么这种设计只会接下来会面临两种情况:
- 服务接口代码中不做翻译,则如果你提供的是 API,则用户拿到你的数据之后,不得不对照着你的文档,才能明白每个值代表什么含义,如果你的接口是给前端使用的,那么前端必须按照你的接口文档去维护一个翻译列表。更糟糕的是,如果有一天你的状态多了一种,比如失败重试太多次后中止任务[5-已中止],如果前端未更新翻译列表,则好的情况是前端做了容错,在页面上显示 5,坏的情况是直接就在页面显示了 undefined。
- 服务接口代码中做翻译,你可能需要在代码中写大量的代码做 IN OUT 两个方向上的翻译工作。
而如果设计之初,就将该状态字段的值设置为了有意义的字符串枚举呢?如[READY;RUNNING;RETRYING;FAILBACK;DONE],则按照上述情况1,用户拿到数据后,一目了然就能知晓当前状态是怎样的。如果是前端使用接口,即使状态增加[ABORT]时,即使前端未及时更新翻译列表,页面上直接显示 ABORT,也不至于使用户莫名其妙。 总而言之,以数字作为枚举值的做法,绝大多数情况都可以归为陋习(不排除个别特殊情况)。回到错误码的讨论上,抛弃旧有的以数字作为错误码的古董观念吧!我们定义错误码枚举包含两个字段: code 字段给出简明扼要的错误提示, message