简介
Graceful Response 是一个面向 Spring Boot 技术栈的优雅响应处理器。它提供一站式统一返回值封装、全局异常处理、自定义异常错误码等功能。借助它进行 Web 接口开发,既能显著提升效率,也能让代码逻辑更清晰、可读性更好。
本项目案例工程代码:https://github.com/feiniaojin/graceful-response-example.git ,注意选择最新版本分支。
| Spring Boot 版本 |
Graceful Response 版本 |
graceful-response-example 分支 |
| 2.x |
3.2.1-boot2 |
3.2.0-boot2 |
| 3.x |
3.2.1-boot3 |
3.2.0-boot3 |
注意,3.2.1-boot2 版本的 Graceful Response 源码由单独仓库维护,地址为 https://github.com/feiniaojin/graceful-response-boot2 。3.2.1-boot2 和 3.2.1-boot3 除了支持的 Spring Boot 版本不同,其他实现完全一致。Maven 引用时只需根据对应的 Spring Boot 版本选择 Graceful Response 的 version,两者的 groupId、artifactId 一致。
快速入门
Spring Boot 接口开发现状
目前,业界使用 Spring Boot 进行 接口开发 时,往往存在效率低下、重复劳动、可读性差等问题。下面这段伪代码大家应该很熟悉,很多项目里的 Controller 接口都长这样:
@Controller
public class Controller {
@GetMapping("/query")
@ResponseBody
public Response query(Map<String, Object> paramMap) {
Response res = new Response();
try {
// 1.校验 params 参数合法性,包括非空校验、长度校验等
if (illegal(paramMap)) {
res.setCode(1);
res.setMsg("error");
return res;
}
// 2.调用 Service 的一系列操作,得到查询结果
Object data = service.query(params);
// 3.将操作结果设置到 res 对象中
res.setData(data);
res.setCode(0);
res.setMsg("ok");
return res;
} catch (Exception e) {
// 4.异常处理:手写 try...catch,有错误码时还需要手工填充
res.setCode(1);
res.setMsg("error");
return res;
}
}
}
这段代码存在哪些问题?
第一个问题,效率低下。Controller 层的代码本来应该尽量简洁,上面的伪代码其实只是为了把查询结果封装成统一格式返回。例如以下响应体:
{
"code": 0,
"msg": "ok",
"data": {
"id": 1,
"name": "username"
}
}
查询过程中如果发生异常,就需要在 Controller 手工捕获,再人工设置错误码,用同样的格式封装后返回。也就是说,除了调用 service 层的 query 方法那一行,其余大量代码都在做结果封装,冗余且低价值。
第二个问题,重复劳动。捕获异常、封装执行结果这些操作每个接口都会重复一遍。
第三个问题,可读性低。核心业务代码被淹没在大量冗余代码里,阅读起来就像大海捞针。
借助 Graceful Response 组件,可以解决这些问题。
快速入门
引入 Graceful Response 组件
Graceful Response 已发布到 Maven 中央仓库,可以直接引入。
<dependency>
<groupId>com.feiniaojin</groupId>
<artifactId>graceful-response</artifactId>
<version>{latest.version}</version>
</dependency>
| Spring Boot 版本 |
Graceful Response 最新版本 |
| 2.x |
3.2.1-boot2 |
| 3.x |
3.2.1-boot3 |
启用 Graceful Response
在启动类中引入 @EnableGracefulResponse 注解,即可启用组件。
@EnableGracefulResponse
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
Controller 层
引入 Graceful Response 后,不需要手工封装查询结果,直接返回实际结果即可,组件会自动完成封装。
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();
}
}
示例中 Controller 方法直接返回 UserInfoView 对象,没有做封装操作,但经过 Graceful Response 处理后,仍能得到以下响应:
{
"status": {
"code": "0",
"msg": "ok"
},
"payload": {
"id": 1,
"name": "name1"
}
}
对于命令操作,默认不需要返回数据,因此命令操作的方法返回值应为 void。Graceful Response 对返回值类型 void 的方法同样会自动封装。
public class Controller {
@RequestMapping("/command")
@ResponseBody
public void command() {
// 业务操作
}
}
成功调用该接口后返回:
{
"status": {
"code": "200",
"msg": "success"
},
"payload": {}
}
Service 层
引入 Graceful Response 之前,有些开发者在 Service 层方法中直接返回 Response,淹没了方法正常的返回值。
Response 的定义如下:
// lombok 注解
@Data
public class Response {
private String code;
private String msg;
private Object data;
}
直接返回 Response 的 Service 层方法:
/**
* 直接返回 Response 的 Service
* 不规范
*/
public interface Service {
public Response commandMethod(Command command);
}
Graceful Response 引入 @ExceptionMapper 注解,通过它把异常和错误码关联起来。这样 Service 方法就不需要再维护响应码,直接抛出业务异常,由组件完成异常与响应码的映射。
@ExceptionMapper 用法如下:
/**
* NotFoundException 的定义,使用 @ExceptionMapper 注解修饰
* code:代表接口的异常码
* msg:代表接口的异常提示
*/
@ExceptionMapper(code = "1404", msg = "找不到对象")
public class NotFoundException extends RuntimeException {
}
Service 接口定义:
public interface QueryService {
UserInfoView queryOne(Query query);
}
Service 接口实现:
public class QueryServiceImpl implements QueryService {
@Resource
private UserInfoMapper mapper;
@Override
public UserInfoView queryOne(Query query) {
UserInfo userInfo = mapper.findOne(query.getId());
if (Objects.isNull(userInfo)) {
// 这里直接抛自定义异常
throw new NotFoundException();
}
// ……后续业务操作
}
}
当 queryOne 方法抛出 NotFoundException 时,Graceful Response 会捕获异常,将异常对应的异常码和异常信息封装到统一响应对象中,最终返回:
{
"status": {
"code": "1404",
"msg": "找不到对象"
},
"payload": {}
}
参数校验
Graceful Response 对 JSR-303 数据校验规范和 Hibernate Validator 做了增强。它自身不提供参数校验功能,但配合 Hibernate Validator 使用后,可以通过 @ValidationStatusCode 注解为参数校验结果指定业务码,并统一封装返回。
例如 UserInfoQuery:
@Data
public class UserInfoQuery {
@NotNull(message = "userName is null !")
@Length(min = 6, max = 12)
@ValidationStatusCode(code = "520")
private String userName;
}
上例中 userName 定义了两个校验规则。未引入 Graceful Response 时,校验失败会直接抛异常;如果引入组件但没有加 @ValidationStatusCode,则以默认错误码返回。这里因为指定了 code=520,userName 字段任意校验不通过时,都会统一返回错误码 520:
{
"status": {
"code": "520",
"msg": "userName is null !"
},
"payload": {}
}
对于 Controller 层直接校验方法入参的场景,Graceful Response 也做了增强:
public class Controller {
@RequestMapping("/validateMethodParam")
@ResponseBody
@ValidationStatusCode(code = "1314")
public void validateMethodParam(
@NotNull(message = "userId不能为空") Long userId,
@NotNull(message = "userName不能为空") Long userName) {
// 省略业务逻辑
}
}
如果该方法入参校验触发 userId 和 userName 的校验异常,返回错误码 1314:
{
"status": {
"code": "1314",
"msg": "userId不能为空"
},
"payload": {}
}
自定义 Response 格式
Graceful Response 内置两种响应风格,通过 graceful-response.response-style 配置。
配置 graceful-response.response-style=0(或不配置,默认),返回:
{
"status": {
"code": 1007,
"msg": "有内鬼,终止交易"
},
"payload": {
}
}
配置 graceful-response.response-style=1,返回:
{
"code": "1404",
"msg": "not found",
"data": {
}
}
如果这两种格式都不满足业务需要,Graceful Response 也支持 自定义 Response 格式。具体技术实现可查阅组件文档。
本项目还提供以下进阶功能:
- 第三方组件集成(Swagger、Actuator 等)
- 自定义响应
- 异常请求放行
- 异常别名
- 常用配置项
目前该组件在 GitHub 上已经有两百多 Star,很多朋友已经开始使用,可以通过下方链接了解:
https://github.com/feiniaojin/graceful-response
如果你在落地统一响应封装时遇到问题,也欢迎到云栈社区一起交流。