一站式统一返回值封装、异常处理、异常错误码解决方案—最强的Sping Boot接口优雅响应处理器
作者:京东物流 覃玉杰
1. 简介
Graceful Response是一个Spring Boot体系下的优雅响应处理器,提供一站式统一返回值封装、异常处理、异常错误码等功能。
使用Graceful Response进行web接口开发不仅可以节省大量的时间,还可以提高代码质量,使代码逻辑更清晰。
强烈推荐你花3分钟学会它!
Graceful Response的Github地址: https://github.com/feiniaojin/graceful-response ,欢迎star!
Graceful Response的案例工程代码:https://github.com/feiniaojin/graceful-response-example.git
2. Spring Boot Web API接口数据返回的现状
我们进行Spring Boo Web API接口开发时,通常大部分的Controller代码是这样的:
public class Controller {
@GetMapping("/query")
@ResponseBody
public Response query(Parameter params) {
Response res = new Response();
try {
//1.校验params参数,非空校验、长度校验
if (illegal(params)) {
res.setCode(1);
res.setMsg("error");
return res;
}
//2.调用Service的一系列操作
Data data = service.query(params);
//3.执行正确时,将操作结果设置到res对象中
res.setData(data);
res.setCode(0);
res.setMsg("ok");
return res;
} catch (BizException1 e) {
//4.异常处理:一堆丑陋的try...catch,如果有错误码的,还需要手工填充错误码
res.setCode(1024);
res.setMsg("error");
return res;
} catch (BizException2 e) {
//4.异常处理:一堆丑陋的try...catch,如果有错误码的,还需要手工填充错误码
res.setCode(2048);
res.setMsg("error");
return res;
} catch (Exception e) {
//4.异常处理:一堆丑陋的try...catch,如果有错误码的,还需要手工填充错误码
res.setCode(1);
res.setMsg("error");
return res;
}
}
}
这段代码存在什么问题呢?真正的业务逻辑被冗余代码淹没,可读性太差。
真正执行业务的代码只有
Data data=service.query(params);
其他代码不管是正常执行还是异常处理,都是为了异常封装、把结果封装为特定的格式,例如以下格式:
{
"code": 0,
"msg": "ok",
"data": {
"id": 1,
"name": "username"
}
}
这样的逻辑每个接口都需要处理一遍,都是繁琐的重复劳动。
现在,只需要引入Graceful Response组件并通过@EnableGracefulResponse启用,就可以直接返回业务结果并自动完成response的格式封装。
以下是使用Graceful Response之后的代码,实现同样的返回值封装、异常处理、异常错误码功能,但可以看到代码变得非常简洁,可读性非常强。
public class Controller {
@GetMapping("/query")
@ResponseBody
public Data query(Parameter params) {
return service.query(params);
}
}
3. 快速入门
3.1 引入maven依赖
graceful-response已发布至maven中央仓库,可以直接引入到项目中,maven依赖如下:
<dependency>
<groupId>com.feiniaojin</groupId>
<artifactId>graceful-response</artifactId>
<version>2.0</version>
</dependency>
3.2 在启动类中引入@EnableGracefulResponse注解
@EnableGracefulResponse
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
3.3 Controller方法直接返回结果
• 普通的查询
@Controller
public class Controller {
@RequestMapping("/get")
@ResponseBody
public UserInfoView get(Long id) {
log.info("id={}", id);
return UserInfoView.builder().id(id).name("name" + id).build();
}
}
UserInfoView的源码:
@Data
@Builder
public class UserInfoView {
private Long id;
private String name;
}
这个接口直接返回了 UserInfoView
的实例对象,调用接口时,Graceful Response将自动封装为以下格式:
{
"status": {
"code": "0",
"msg": "ok"
},
"payload": {
"id": 1,
"name": "name1"
}
}
可以看到UserInfoView
被自动封装到payload字段中。
Graceful Response提供了两种风格的Response,可以通过在application.properties文件中配置gr.responseStyle=1,将以以下的格式进行返回:
{
"code": "0",
"msg": "ok",
"data": {
"id": 1,
"name": "name1"
}
}
如果这两种风格也不能满足需要,我们还可以根据自己的需要进行自定义返回的Response格式。详细见本文 4.3自定义Respnse格式。
• 异常处理的场景
通过Graceful Response,我们不需要专门在Controller中处理异常,详细见 4.1 Graceful Response异常错误码处理。
• 返回值为空的场景
某些Command类型的方法只执行修改操作,不返回数据,这个时候我们可以直接在Controller中返回void,Graceful Response会自动封装默认的操作成功Response报文。
@Controller
public class Controller {
@RequestMapping("/void")
@ResponseBody
public void testVoidResponse() {
//省略业务操作
}
}
testVoidResponse
方法的返回时void,调用这个接口时,将返回:
{
"status": {
"code": "200",
"msg": "success"
},
"payload": {}
}
3.4 Service方法业务处理
在引入Graceful Response后,Service层的方法的可读性可以得到极大的提升。
• 接口直接返回业务数据类型,而不是Response,更具备可读性
public interface ExampleService {
UserInfoView query1(Query query);
}
• Service接口实现类中,直接抛自定义的业务异常,Graceful Response将其转化为返回错误码和错误提示
public class ExampleServiceImpl implements ExampleService {
@Resource
private UserInfoMapper mapper;
public UserInfoView query1(Query query) {
UserInfo userInfo = mapper.findOne(query.getId());
if (Objects.isNull(userInfo)) {
//这里直接抛自定义异常,异常通过@ExceptionMapper修饰,提供异常码和异常提示
throw new NotFoundException();
}
// 省略后续业务操作
}
}
/**
* NotFoundException的定义,使用@ExceptionMapper注解修饰
* code:代表接口的异常码
* msg:代表接口的异常提示
*/
@ExceptionMapper(code = "1404", msg = "找不到对象")
public class NotFoundException extends RuntimeException {
}
//Controller不再捕获处理异常
@RequestMapping("/get")
@ResponseBody
public UserInfoView get(Query query)) {
return exampleService.query1(query);
}
当Service方法抛出NotFoundException异常时,接口将直接返回错误码,不需要手工set,极大地简化了异常处理逻辑。
{
"status": {
"code": "1404",
"msg": "找不到对象"
},
"payload": {}
}
验证:启动example工程后,请求http://localhost:9090/example/notfound
4. 进阶用法
4.1 Graceful Response异常错误码处理
以下是使用Graceful Response进行异常、错误码处理的开发步骤。
• 创建自定义异常
通过继承RuntimeException类创建自定义的异常,采用 @ExceptionMapper
注解修饰,注解的 code
属性为返回码,msg
属性为错误提示信息。
关于是继承RuntimeException还是继承Exception,读者可以根据实际情况去选择,Graceful Response对两者都支持。
@ExceptionMapper(code = "1007", msg = "有内鬼,终止交易")
public static final class RatException extends RuntimeException {
}
• Service执行具体逻辑
Service执行业务逻辑的过程中,需要抛异常的时候直接抛出去即可。由于已经通过@ExceptionMapper定义了该异常的错误码,我们不需要再单独的维护异常码枚举与异常类的关系。
//Service层伪代码
public class Service {
public void illegalTransaction() {
//需要抛异常的时候直接抛
if (check()) {
throw new RatException();
}
doIllegalTransaction();
}
}
Controller层调用Service层伪代码:
public class Controller {
@RequestMapping("/test3")
public void test3() {
//Controller中不会进行异常处理,也不会手工set错误码,只关心核心操作,其他的统统交给Graceful Response
exampleService.illegalTransaction();
}
}
在浏览器中请求controller的/test3方法,有异常时将会返回:
{
"status": {
"code": "1007",
"msg": "有内鬼,终止交易"
},
"payload": {
}
}
4.2 外部异常别名
案例工程( https://github.com/feiniaojin/graceful-response-example.git )启动后, 通过浏览器访问一个不存在的接口,例如 http://localhost:9090/example/get2?id=1
如果没开启Graceful Response,将会跳转到404页面,主要原因是应用内部产生了 NoHandlerFoundException
异常。如果开启了Graceful Response,默认会返回code=1的错误码。
这类非自定义的异常,如果需要自定义一个错误码返回,将不得不对每个异常编写Advice逻辑,在Advice中设置错误码和提示信息,这样做也非常繁琐。
Graceful Response可以非常轻松地解决给这类外部异常定义错误码和提示信息的问题。
以下为操作步骤:
• 创建异常别名,并用 @ExceptionAliasFor
注解修饰
@ExceptionAliasFor(code = "1404", msg = "Not Found", aliasFor = NoHandlerFoundException.class)
public class NotFoundException extends RuntimeException {
}
code:捕获异常时返回的错误码
msg:异常提示信息
aliasFor:表示将成为哪个异常的别名,通过这个属性关联到对应异常。
• 注册异常别名
创建一个继承了AbstractExceptionAliasRegisterConfig的配置类,在实现的registerAlias方法中进行注册。
@Configuration
public class GracefulResponseConfig extends AbstractExceptionAliasRegisterConfig {
@Override
protected void registerAlias(ExceptionAliasRegister aliasRegister) {
aliasRegister.doRegisterExceptionAlias(NotFoundException.class);
}
}
• 浏览器访问不存在的URL
再次访问 http://localhost:9090/example/get2?id=1 ,服务端将返回以下json,正是在ExceptionAliasFor中定义的内容
{
"code": "1404",
"msg": "not found",
"data": {
}
}
4.3 自定义Response格式
Graceful Response内置了两种风格的响应格式,可以在application.properties文件中通过gr.responseStyle
进行配置
• gr.responseStyle=0,或者不配置(默认情况)
将以以下的格式进行返回:
{
"status": {
"code": "1007",
"msg": "有内鬼,终止交易"
},
"payload": {
}
}
• gr.responseStyle=1
将以以下的格式进行返回:
{
"code": "1404",
"msg": "not found",
"data": {
}
}
• 自定义响应格式
如果以上两种格式均不能满足业务需要,可以通过自定义去满足,Response
例如以下响应:
public class CustomResponseImpl implements Response {
private String code;
private Long timestamp = System.currentTimeMillis();
private String msg;
private Object data = Collections.EMPTY_MAP;
@Override
public void setStatus(ResponseStatus statusLine) {
this.code = statusLine.getCode();
this.msg = statusLine.getMsg();
}
@Override
@JsonIgnore
public ResponseStatus getStatus() {
return null;
}
@Override
public void setPayload(Object payload) {
this.data = payload;
}
@Override
@JsonIgnore
public Object getPayload() {
return null;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Long getTimestamp() {
return timestamp;
}
}
注意,不需要返回的属性可以返回null或者加上@JsonIgnore注解
• 配置gr.responseClassFullName
将CustomResponseImpl的全限定名配置到gr.responseClassFullName属性。
gr.responseClassFullName=com.feiniaojin.gracefuresponse.example.config.CustomResponseImpl
注意,配置gr.responseClassFullName后,gr.responseStyle将不再生效。
实际的响应报文如下:
{
"code":"200",
"timestamp":1682489591319,
"msg":"success",
"data":{
}
}
如果还是不能满足需求,那么可以考虑同时自定义实现Response和ResponseFactory这两个接口。
5. 常用配置
Graceful Response在版本迭代中,根据用户反馈提供了一些常用的配置项,列举如下:
• gr.printExceptionInGlobalAdvice是否打印异常日志,默认为false
• gr.responseClassFullName自定义Response类的全限定名,默认为空。 配置gr.responseClassFullName后,gr.responseStyle将不再生效
• gr.responseStyleResponse风格,不配置默认为0
• gr.defaultSuccessCode自定义的成功响应码,不配置则为0
• gr.defaultSuccessMsg自定义的成功提示,默认为ok
• gr.defaultFailCode自定义的失败响应码,默认为1
• gr.defaultFailMsg自定义的失败提示,默认为error
一站式统一返回值封装、异常处理、异常错误码解决方案—最强的Sping Boot接口优雅响应处理器的更多相关文章
- Spring Boot API 统一返回格式封装
今天给大家带来的是Spring Boot API 统一返回格式封装,我们在做项目的时候API 接口返回是需要统一格式的,只有这样前端的同学才可对接口返回的数据做统一处理,也可以使前后端分离 模式的开发 ...
- Spring Boot 2.x(六):优雅的统一返回值
目录 为什么要统一返回值 ReturnVO ReturnCode 使用ReturnVO 使用AOP进行全局异常的处理 云撸猫 公众号 为什么要统一返回值 在我们做后端应用的时候,前后端分离的情况下,我 ...
- spring boot 接口返回值封装
Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...
- Redis Windows 服务启动异常 错误码1067
https://blog.csdn.net/after_you/article/details/62215163 Redis Windows 服务启动异常 错误码1067 下载了Redis 2.8.2 ...
- Spring/SpringBoot定义统一异常错误码返回
配置 大致说下流程, 首先我们自定义一个自己的异常类CustomException,继承RuntimeException.再写一个异常管理类ExceptionManager,用来抛出自定义的异常. 然 ...
- 统一返回对象封装和统一异常捕获封装springboot starter
好久没有更新文章了,高龄开发没什么技术,去了外包公司后没怎么更新文章了.今天分享下统一处理starter,相信开发web系统的时候都是会涉及到前后端的交互,而后端返回数据的时候一般都会统一封装一个返回 ...
- JAVAEE——SpringMVC第二天:高级参数绑定、@RequestMapping、方法返回值、异常处理、图片上传、Json交互、实现RESTful、拦截器
1. 课前回顾 https://www.cnblogs.com/xieyupeng/p/9093661.html 2. 课程计划 1.高级参数绑定 a) 数组类型的参数绑定 b) List类型的绑定 ...
- Spring Boot 统一返回结果及异常处理
在 Spring Boot 构建电商基础秒杀项目 (三) 通用的返回对象 & 异常处理 基础上优化.调整 一.通用类 1.1 通用的返回对象 public class CommonReturn ...
- Java异常封装(自定义错误码和描写叙述,附源代码)
真正工作了才发现.Java里面的异常在真正工作中使用还是十分普遍的. 什么时候该抛出什么异常,这个是必须知道的. 当然真正工作里面主动抛出的异常都是经过分装过的,自己能够定义错误码和异常描写叙述. 以 ...
- C#中返回值封装
在平时开发过程中常常需要取一个方法的返回值,BOSS写了一个返回值类,做个练习以备不时之需: 返回值支持泛型和非泛型 先贴上代码: 非泛型返回值类: using System; using Syste ...
随机推荐
- docker&docker-compose安装
一.docker安装 1.通过 uname -r 命令查看当前的内核版本,Docker 要求 CentOS 系统的内核版本高于 3.10 uname -r 2.查看系统是否安装过docker yum ...
- 生产环境实现Docker部署宝塔面板
生产环境中,为了避免极小概率的数据丢失,我们将容器内的宝塔文件映射到宿主机的目录中(您之后安装的 Nginx.MySQL 等服务均会挂载到宿主机目录).该方法是 Docker 部署宝塔面板的最优方案, ...
- 【MSSQL】AlwaysOn集群增加发布订阅
在现有AlwaysOn集群增加发布订阅节点 配置 前提 节点1.节点2在AlwaysOn集群,节点3作为集群外节点使用订阅复制集群数据同步 发布对象必须要有主键 步骤 登录节点3配置分发distrib ...
- 音频和视频流最佳选择?SRT 协议解析及报文识别
我们所知道 SRT 是由 Haivision 和 Wowza 开发的开源视频流协议.很多人会认为在不久的将来,它被是 RTMP 的替代品.因为 RTMP 协议安全性稍低,延迟相对较高 ,而相对于 SR ...
- P8112 符文破译
题目描述 将字符串 \(T\) 拆成若干个子串,使这些子串为字符串 \(S\) 的前缀,要求拆分形成的子串数最小. 思路整理 实际上并不需要倒着枚举,也不需要线段树,更不需要 Z 函数. 如果你做过 ...
- Django-5
Django-5 1.Coookie 1.1 什么是cookie Cookie是储存在浏览器端的一小段文本数据(键值对). 被广泛用于在网站之间传输信息, 当您访问一个网站时,它会将一个Cookie发 ...
- Java面试——开源框架知识
一.简单讲讲 Tomcat结构,以及其类加载器流程,线程模型等 [1]模块组成结构:Tomcat 的核心组件就 Connector 和 Container,一个Connector+一个Containe ...
- SpringBoot——自定义start
更多内容,前往 IT-BLOG 一.Mybatis 实现 start 的原理 首先在写一个自定义的 start 之前,我们先参考下 Mybatis 是如何整合 SpringBoot:mybatis-s ...
- Redis事件机制(未写完)
Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件: 文件事件:Redis通过套接字与客户端连接,文件事件是服务器对套接字操作的抽象. 时间事件:Redis服务器中的一些操作需要给定的时间 ...
- .Net 6.0定义全局当前身份缓存对象
背景: 当前身份缓存对象顾名思义就是:当前登录的用户身份对象,那它解决了什么问题呢?其实在我们日常开发过程中经常能用的到几乎是必备的,就比如我给某个表插入数据时需要创建人或者一些权限的访问,都得用到当 ...