ToB企服应用市场:ToB评测及商务社交产业平台

标题: 一站式统一返回值封装、异常处理、异常错误码解决方案—最强的Sping Boot接 [打印本页]

作者: tsx81429    时间: 2023-5-9 10:49
标题: 一站式统一返回值封装、异常处理、异常错误码解决方案—最强的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代码是这样的:
  1. public class Controller {
  2.     @GetMapping("/query")
  3.     @ResponseBody
  4.     public Response query(Parameter params) {
  5.         Response res = new Response();
  6.         try {
  7.             //1.校验params参数,非空校验、长度校验
  8.             if (illegal(params)) {
  9.                 res.setCode(1);
  10.                 res.setMsg("error");
  11.                 return res;
  12.             }
  13.             //2.调用Service的一系列操作
  14.             Data data = service.query(params);
  15.             //3.执行正确时,将操作结果设置到res对象中
  16.             res.setData(data);
  17.             res.setCode(0);
  18.             res.setMsg("ok");
  19.             return res;
  20.         } catch (BizException1 e) {
  21.             //4.异常处理:一堆丑陋的try...catch,如果有错误码的,还需要手工填充错误码
  22.             res.setCode(1024);
  23.             res.setMsg("error");
  24.             return res;
  25.         } catch (BizException2 e) {
  26.             //4.异常处理:一堆丑陋的try...catch,如果有错误码的,还需要手工填充错误码
  27.             res.setCode(2048);
  28.             res.setMsg("error");
  29.             return res;
  30.         } catch (Exception e) {
  31.             //4.异常处理:一堆丑陋的try...catch,如果有错误码的,还需要手工填充错误码
  32.             res.setCode(1);
  33.             res.setMsg("error");
  34.             return res;
  35.         }
  36.     }
  37. }
复制代码
这段代码存在什么问题呢?真正的业务逻辑被冗余代码淹没,可读性太差。
真正执行业务的代码只有
  1. Data data=service.query(params);
复制代码
其他代码不管是正常执行还是异常处理,都是为了异常封装、把结果封装为特定的格式,例如以下格式:
  1. {
  2.   "code": 0,
  3.   "msg": "ok",
  4.   "data": {
  5.     "id": 1,
  6.     "name": "username"
  7.   }
  8. }
复制代码
这样的逻辑每个接口都需要处理一遍,都是繁琐的重复劳动。
现在,只需要引入Graceful Response组件并通过@EnableGracefulResponse启用,就可以直接返回业务结果并自动完成response的格式封装。
以下是使用Graceful Response之后的代码,实现同样的返回值封装、异常处理、异常错误码功能,但可以看到代码变得非常简洁,可读性非常强。
  1. public class Controller {
  2.     @GetMapping("/query")
  3.     @ResponseBody
  4.     public Data query(Parameter params) {
  5.        return service.query(params);
  6.     }
  7. }
复制代码
3. 快速入门

3.1 引入maven依赖

graceful-response已发布至maven中央仓库,可以直接引入到项目中,maven依赖如下:
  1. <dependency>
  2.     <groupId>com.feiniaojin</groupId>
  3.     <artifactId>graceful-response</artifactId>
  4.     <version>2.0</version>
  5. </dependency>
复制代码
3.2 在启动类中引入@EnableGracefulResponse注解
  1. @EnableGracefulResponse
  2. @SpringBootApplication
  3. public class ExampleApplication {
  4.     public static void main(String[] args) {
  5.         SpringApplication.run(ExampleApplication.class, args);
  6.     }
  7. }
复制代码
3.3 Controller方法直接返回结果

• 普通的查询
  1. @Controller
  2. public class Controller {
  3.     @RequestMapping("/get")
  4.     @ResponseBody
  5.     public UserInfoView get(Long id) {
  6.         log.info("id={}", id);
  7.         return UserInfoView.builder().id(id).name("name" + id).build();
  8.     }
  9. }
复制代码
UserInfoView的源码:
  1. @Data
  2. @Builder
  3. public class UserInfoView {
  4.     private Long id;
  5.     private String name;
  6. }
复制代码
这个接口直接返回了 UserInfoView的实例对象,调用接口时,Graceful Response将自动封装为以下格式:
  1. {
  2.   "status": {
  3.     "code": "0",
  4.     "msg": "ok"
  5.   },
  6.   "payload": {
  7.     "id": 1,
  8.     "name": "name1"
  9.   }
  10. }
复制代码
可以看到UserInfoView被自动封装到payload字段中。
Graceful Response提供了两种风格的Response,可以通过在application.properties文件中配置gr.responseStyle=1,将以以下的格式进行返回:
  1. {
  2.   "code": "0",
  3.   "msg": "ok",
  4.   "data": {
  5.     "id": 1,
  6.     "name": "name1"
  7.   }
  8. }
复制代码
如果这两种风格也不能满足需要,我们还可以根据自己的需要进行自定义返回的Response格式。详细见本文 4.3自定义Respnse格式。
• 异常处理的场景
通过Graceful Response,我们不需要专门在Controller中处理异常,详细见 4.1 Graceful Response异常错误码处理。
• 返回值为空的场景
某些Command类型的方法只执行修改操作,不返回数据,这个时候我们可以直接在Controller中返回void,Graceful Response会自动封装默认的操作成功Response报文。
  1. @Controller
  2. public class Controller {
  3.     @RequestMapping("/void")
  4.     @ResponseBody
  5.     public void testVoidResponse() {
  6.         //省略业务操作
  7.     }
  8. }
复制代码
testVoidResponse方法的返回时void,调用这个接口时,将返回:
  1. {
  2.   "status": {
  3.     "code": "200",
  4.     "msg": "success"
  5.   },
  6.   "payload": {}
  7. }
复制代码
3.4 Service方法业务处理

在引入Graceful Response后,Service层的方法的可读性可以得到极大的提升。
• 接口直接返回业务数据类型,而不是Response,更具备可读性
  1. public interface ExampleService {
  2.     UserInfoView query1(Query query);
  3. }
复制代码
• Service接口实现类中,直接抛自定义的业务异常,Graceful Response将其转化为返回错误码和错误提示
  1. public class ExampleServiceImpl implements ExampleService {
  2.     @Resource
  3.     private UserInfoMapper mapper;
  4.     public UserInfoView query1(Query query) {
  5.         UserInfo userInfo = mapper.findOne(query.getId());
  6.         if (Objects.isNull(userInfo)) {
  7.            //这里直接抛自定义异常,异常通过@ExceptionMapper修饰,提供异常码和异常提示
  8.            throw new NotFoundException();
  9.         }
  10.         // 省略后续业务操作
  11.     }
  12. }
复制代码
  1. /**
  2. * NotFoundException的定义,使用@ExceptionMapper注解修饰
  3. * code:代表接口的异常码
  4. * msg:代表接口的异常提示
  5. */
  6. @ExceptionMapper(code = "1404", msg = "找不到对象")
  7. public class NotFoundException extends RuntimeException {
  8. }
复制代码
  1. //Controller不再捕获处理异常
  2. @RequestMapping("/get")
  3. @ResponseBody
  4. public UserInfoView get(Query query)) {
  5.     return exampleService.query1(query);
  6. }
复制代码
当Service方法抛出NotFoundException异常时,接口将直接返回错误码,不需要手工set,极大地简化了异常处理逻辑。
  1. {
  2.   "status": {
  3.     "code": "1404",
  4.     "msg": "找不到对象"
  5.   },
  6.   "payload": {}
  7. }
复制代码
验证:启动example工程后,请求http://localhost:9090/example/notfound
4. 进阶用法

4.1 Graceful Response异常错误码处理

以下是使用Graceful Response进行异常、错误码处理的开发步骤。
• 创建自定义异常
通过继承RuntimeException类创建自定义的异常,采用 @ExceptionMapper注解修饰,注解的 code属性为返回码,msg属性为错误提示信息。
关于是继承RuntimeException还是继承Exception,读者可以根据实际情况去选择,Graceful Response对两者都支持。
  1. @ExceptionMapper(code = "1007", msg = "有内鬼,终止交易")
  2. public static final class RatException extends RuntimeException {
  3. }
复制代码
• Service执行具体逻辑
Service执行业务逻辑的过程中,需要抛异常的时候直接抛出去即可。由于已经通过@ExceptionMapper定义了该异常的错误码,我们不需要再单独的维护异常码枚举与异常类的关系。
  1. //Service层伪代码
  2. public class Service {
  3.     public void illegalTransaction() {
  4.         //需要抛异常的时候直接抛
  5.         if (check()) {
  6.             throw new RatException();
  7.         }
  8.         doIllegalTransaction();
  9.     }
  10. }
复制代码
Controller层调用Service层伪代码:
  1. public class Controller {
  2.     @RequestMapping("/test3")
  3.     public void test3() {
  4.         //Controller中不会进行异常处理,也不会手工set错误码,只关心核心操作,其他的统统交给Graceful Response
  5.         exampleService.illegalTransaction();
  6.     }
  7. }
复制代码
在浏览器中请求controller的/test3方法,有异常时将会返回:
  1. {
  2.   "status": {
  3.     "code": "1007",
  4.     "msg": "有内鬼,终止交易"
  5.   },
  6.   "payload": {
  7.   }
  8. }
复制代码
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注解修饰
  1. @ExceptionAliasFor(code = "1404", msg = "Not Found", aliasFor = NoHandlerFoundException.class)
  2. public class NotFoundException extends RuntimeException {
  3. }
复制代码
code:捕获异常时返回的错误码
msg:异常提示信息
aliasFor:表示将成为哪个异常的别名,通过这个属性关联到对应异常。
• 注册异常别名
创建一个继承了AbstractExceptionAliasRegisterConfig的配置类,在实现的registerAlias方法中进行注册。
  1. @Configuration
  2. public class GracefulResponseConfig extends AbstractExceptionAliasRegisterConfig {
  3.     @Override
  4.     protected void registerAlias(ExceptionAliasRegister aliasRegister) {
  5.         aliasRegister.doRegisterExceptionAlias(NotFoundException.class);
  6.     }
  7. }
复制代码
• 浏览器访问不存在的URL
再次访问 http://localhost:9090/example/get2?id=1 ,服务端将返回以下json,正是在ExceptionAliasFor中定义的内容
  1. {
  2.   "code": "1404",
  3.   "msg": "not found",
  4.   "data": {
  5.   }
  6. }
复制代码
4.3 自定义Response格式

Graceful Response内置了两种风格的响应格式,可以在application.properties文件中通过gr.responseStyle进行配置
• gr.responseStyle=0,或者不配置(默认情况)
将以以下的格式进行返回:
  1. {
  2.   "status": {
  3.     "code": "1007",
  4.     "msg": "有内鬼,终止交易"
  5.   },
  6.   "payload": {
  7.   }
  8. }
复制代码
• gr.responseStyle=1
将以以下的格式进行返回:
  1. {
  2.   "code": "1404",
  3.   "msg": "not found",
  4.   "data": {
  5.   }
  6. }
复制代码
• 自定义响应格式
如果以上两种格式均不能满足业务需要,可以通过自定义去满足,Response
例如以下响应:
  1. public class CustomResponseImpl implements Response {
  2.     private String code;
  3.     private Long timestamp = System.currentTimeMillis();
  4.     private String msg;
  5.     private Object data = Collections.EMPTY_MAP;
  6.     @Override
  7.     public void setStatus(ResponseStatus statusLine) {
  8.         this.code = statusLine.getCode();
  9.         this.msg = statusLine.getMsg();
  10.     }
  11.     @Override
  12.     @JsonIgnore
  13.     public ResponseStatus getStatus() {
  14.         return null;
  15.     }
  16.     @Override
  17.     public void setPayload(Object payload) {
  18.         this.data = payload;
  19.     }
  20.     @Override
  21.     @JsonIgnore
  22.     public Object getPayload() {
  23.         return null;
  24.     }
  25.     public String getCode() {
  26.         return code;
  27.     }
  28.     public void setCode(String code) {
  29.         this.code = code;
  30.     }
  31.     public String getMsg() {
  32.         return msg;
  33.     }
  34.     public void setMsg(String msg) {
  35.         this.msg = msg;
  36.     }
  37.     public Object getData() {
  38.         return data;
  39.     }
  40.     public void setData(Object data) {
  41.         this.data = data;
  42.     }
  43.     public Long getTimestamp() {
  44.         return timestamp;
  45.     }
  46. }
复制代码
注意,不需要返回的属性可以返回null或者加上@JsonIgnore注解
• 配置gr.responseClassFullName
将CustomResponseImpl的全限定名配置到gr.responseClassFullName属性。
  1. gr.responseClassFullName=com.feiniaojin.gracefuresponse.example.config.CustomResponseImpl
复制代码
注意,配置gr.responseClassFullName后,gr.responseStyle将不再生效。
实际的响应报文如下:
  1. {
  2.     "code":"200",
  3.     "timestamp":1682489591319,
  4.     "msg":"success",
  5.     "data":{
  6.     }
  7. }
复制代码
如果还是不能满足需求,那么可以考虑同时自定义实现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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4