作者:京东物流 覃玉杰

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接口优雅响应处理器的更多相关文章

  1. Spring Boot API 统一返回格式封装

    今天给大家带来的是Spring Boot API 统一返回格式封装,我们在做项目的时候API 接口返回是需要统一格式的,只有这样前端的同学才可对接口返回的数据做统一处理,也可以使前后端分离 模式的开发 ...

  2. Spring Boot 2.x(六):优雅的统一返回值

    目录 为什么要统一返回值 ReturnVO ReturnCode 使用ReturnVO 使用AOP进行全局异常的处理 云撸猫 公众号 为什么要统一返回值 在我们做后端应用的时候,前后端分离的情况下,我 ...

  3. spring boot 接口返回值封装

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

  4. Redis Windows 服务启动异常 错误码1067

    https://blog.csdn.net/after_you/article/details/62215163 Redis Windows 服务启动异常 错误码1067 下载了Redis 2.8.2 ...

  5. Spring/SpringBoot定义统一异常错误码返回

    配置 大致说下流程, 首先我们自定义一个自己的异常类CustomException,继承RuntimeException.再写一个异常管理类ExceptionManager,用来抛出自定义的异常. 然 ...

  6. 统一返回对象封装和统一异常捕获封装springboot starter

    好久没有更新文章了,高龄开发没什么技术,去了外包公司后没怎么更新文章了.今天分享下统一处理starter,相信开发web系统的时候都是会涉及到前后端的交互,而后端返回数据的时候一般都会统一封装一个返回 ...

  7. JAVAEE——SpringMVC第二天:高级参数绑定、@RequestMapping、方法返回值、异常处理、图片上传、Json交互、实现RESTful、拦截器

    1. 课前回顾 https://www.cnblogs.com/xieyupeng/p/9093661.html 2. 课程计划 1.高级参数绑定 a) 数组类型的参数绑定 b) List类型的绑定 ...

  8. Spring Boot 统一返回结果及异常处理

    在 Spring Boot 构建电商基础秒杀项目 (三) 通用的返回对象 & 异常处理 基础上优化.调整 一.通用类 1.1 通用的返回对象 public class CommonReturn ...

  9. Java异常封装(自定义错误码和描写叙述,附源代码)

    真正工作了才发现.Java里面的异常在真正工作中使用还是十分普遍的. 什么时候该抛出什么异常,这个是必须知道的. 当然真正工作里面主动抛出的异常都是经过分装过的,自己能够定义错误码和异常描写叙述. 以 ...

  10. C#中返回值封装

    在平时开发过程中常常需要取一个方法的返回值,BOSS写了一个返回值类,做个练习以备不时之需: 返回值支持泛型和非泛型 先贴上代码: 非泛型返回值类: using System; using Syste ...

随机推荐

  1. docker&docker-compose安装

    一.docker安装 1.通过 uname -r 命令查看当前的内核版本,Docker 要求 CentOS 系统的内核版本高于 3.10 uname -r 2.查看系统是否安装过docker yum ...

  2. 生产环境实现Docker部署宝塔面板

    生产环境中,为了避免极小概率的数据丢失,我们将容器内的宝塔文件映射到宿主机的目录中(您之后安装的 Nginx.MySQL 等服务均会挂载到宿主机目录).该方法是 Docker 部署宝塔面板的最优方案, ...

  3. 【MSSQL】AlwaysOn集群增加发布订阅

    在现有AlwaysOn集群增加发布订阅节点 配置 前提 节点1.节点2在AlwaysOn集群,节点3作为集群外节点使用订阅复制集群数据同步 发布对象必须要有主键 步骤 登录节点3配置分发distrib ...

  4. 音频和视频流最佳选择?SRT 协议解析及报文识别

    我们所知道 SRT 是由 Haivision 和 Wowza 开发的开源视频流协议.很多人会认为在不久的将来,它被是 RTMP 的替代品.因为 RTMP 协议安全性稍低,延迟相对较高 ,而相对于 SR ...

  5. P8112 符文破译

    题目描述 将字符串 \(T\) 拆成若干个子串,使这些子串为字符串 \(S\) 的前缀,要求拆分形成的子串数最小. 思路整理 实际上并不需要倒着枚举,也不需要线段树,更不需要 Z 函数. 如果你做过 ...

  6. Django-5

    Django-5 1.Coookie 1.1 什么是cookie Cookie是储存在浏览器端的一小段文本数据(键值对). 被广泛用于在网站之间传输信息, 当您访问一个网站时,它会将一个Cookie发 ...

  7. Java面试——开源框架知识

    一.简单讲讲 Tomcat结构,以及其类加载器流程,线程模型等 [1]模块组成结构:Tomcat 的核心组件就 Connector 和 Container,一个Connector+一个Containe ...

  8. SpringBoot——自定义start

    更多内容,前往 IT-BLOG 一.Mybatis 实现 start 的原理 首先在写一个自定义的 start 之前,我们先参考下 Mybatis 是如何整合 SpringBoot:mybatis-s ...

  9. Redis事件机制(未写完)

    Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件: 文件事件:Redis通过套接字与客户端连接,文件事件是服务器对套接字操作的抽象. 时间事件:Redis服务器中的一些操作需要给定的时间 ...

  10. .Net 6.0定义全局当前身份缓存对象

    背景: 当前身份缓存对象顾名思义就是:当前登录的用户身份对象,那它解决了什么问题呢?其实在我们日常开发过程中经常能用的到几乎是必备的,就比如我给某个表插入数据时需要创建人或者一些权限的访问,都得用到当 ...