# graceful-response **Repository Path**: gujoel/graceful-response ## Basic Information - **Project Name**: graceful-response - **Description**: REST接口优雅响应处理器,为Sping Boot应用一站式提供统一返回值封装、异常处理、异常错误码等功能 - **Primary Language**: Java - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 36 - **Created**: 2022-10-02 - **Last Updated**: 2022-10-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README [![vAPppF.jpg](https://s1.ax1x.com/2022/08/01/vAPppF.jpg)](https://imgtu.com/i/vAPppF) ![](https://img.shields.io/github/license/feiniaojin/graceful-response) [![GitHub stars](https://img.shields.io/github/stars/feiniaojin/graceful-response)](https://github.com/feiniaojin/graceful-response/stargazers) [![GitHub forks](https://img.shields.io/github/forks/feiniaojin/graceful-response)](https://github.com/feiniaojin/graceful-response/network) ![](https://img.shields.io/github/issues/feiniaojin/graceful-response) ![Maven Central](https://img.shields.io/maven-central/v/com.feiniaojin.ddd.ecosystem/graceful-response) # 一、简介 Graceful Response是一个Spring Boot体系下的优雅响应处理器。 # 二、Java Web API接口数据返回的现状及解决方案 通常我们进行Java Web API接口时,大部分的Controller代码是这样的: ```java @GetMapping @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; } } ``` 这段代码存在什么问题呢? * 真正的业务逻辑被冗余代码淹没 可以看到,真正执行业务的代码只有 ```java Data data=service.query(params); ``` 其他代码都是为了异常封装、把结果封装为特定的格式,例如以下两种格式: ```json { "code": 0, "msg": "ok", "data": { "id": 1, "name": "username" } } ``` 或者 ```json { "status": { "code": 0, "msg": "ok" }, "payload": { "id": 1, "name": "username" } } ``` 而且,这样的逻辑每个接口都需要处理一遍,都是繁琐的重复劳动。 现在,在引入**Graceful Response**组件后,我们只要直接返回业务结果,**Graceful Response**即可自动完成封装。 以下是一个简单的案例。 接口Controller代码: ```java @RequestMapping("/get") @ResponseBody public UserInfoView get(Long id) { log.info("id=" + id); return UserInfoView.builder().id(id).name("name" + id).build(); } ``` 这个接口我们直接返回了`UserInfoView`的实例对象,并没有封装到Response中,但是我们调用接口时,返回的却是按照为以下格式封装好的响应: ```json { "status":{ "code":"0", "msg":"ok" }, "payload":{ "id":1, "name":"name1" } } ``` 我们的返回结果被自动封装到payload字段中。 注:返回结果的格式是可以自定义的,以上的格式只是作者习惯采用的,我们可以根据自己的需要进行自定义返回的Response格式。 在本组件的案例工程( https://github.com/feiniaojin/graceful-response-example.git )中, 提供了封装为以下格式的`ResponseFactory`,详细见`CustomResponseFactoryImpl`。 graceful-response-example中自定义的返回值格式: ```json { "code":"0", "msg":"ok", "data":{ "id":1, "name":"name1" } } ``` * 手工进行错误码封装 在上面的示例代码中可以看到,为了根据不同的异常返回不同的错误码,捕获了多个异常,并且手工set错误码。 ```java try{ //省略业务操作代码…… }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; } ``` 我们可以通过**Graceful Response**优化这个过程:直接抛异常,**Graceful Response**捕获异常、封装Response并set错误码和提示信息。 以下是使用**Graceful Response**进行异常、错误码处理的开发步骤。 (1)创建自定义异常,采用`@ExceptionMapper`注解修饰,注解的`code`属性为返回码,`msg`属性为错误提示信息 ```java @ExceptionMapper(code = 1007, msg = "有内鬼,终止交易") public static final class RatException extends RuntimeException { } ``` (2)service执行具体逻辑,需要抛异常的时候直接抛出去即可,不需要在关心异常与错误码关联的问题 ```java public void illegalTransaction() { //需要抛异常的时候直接抛 if (hasRat()) { logger.error("有内鬼终止交易"); throw new RatException(); } doIllegalTransaction(); } ``` (3)controller调用service ```java @RequestMapping("/test3") public void test3(){ logger.info("test3: RuntimeException"); //Controlelr中不会进行异常处理,也不会手工set错误码,只关心核心操作,其他的统统交给Graceful Response exampleService.illegalTransaction(); } ``` (4)在浏览器中请求controller的/test3方法,有异常时将会返回: ```json { "status":{ "code":1007, "msg":"有内鬼,终止交易" }, "payload":{ } } ``` # 三、外部异常别名 案例工程( 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可以非常轻松地解决给这类外部异常定义错误码和提示信息的问题。 以下为操作步骤: (1)创建异常别名,并用`@ExceptionAliasFor`注解修饰 ```java @ExceptionAliasFor(code = "1404", msg = "not found", aliasFor = NoHandlerFoundException.class) public class NotFoundException extends RuntimeException { } ``` code为发生NoHandlerFoundException时的错误码,msg为提示信息,aliasFor表示将成为哪个异常的别名。 (2)注册异常别名 创建一个继承了AbstractExceptionAliasRegisterConfig的配置类,在实现的registerAlias方法中进行注册。 ```java @Configuration public class GracefulResponseConfig extends AbstractExceptionAliasRegisterConfig { @Override protected void registerAlias(ExceptionAliasRegister aliasRegister) { aliasRegister.doRegisterExceptionAlias(NotFoundException.class); } } ``` (3)浏览器访问不存在的URL 再次访问 http://localhost:9090/example/get2?id=1 ,服务端将返回以下json,正是在ExceptionAliasFor中定义的内容 ```json { "code":"1404", "msg":"not found", "data":{ } } ``` # 四、快速入门 第一步:引入maven依赖 **graceful-response**已发布至maven中央仓库,可以直接引入到项目中,maven依赖如下: ```xml com.feiniaojin.ddd.ecosystem graceful-response 1.0 ``` 第二步:开启Graceful Response 在Spring Boot的配置类上,引入@EnableGracefulResponse注解 ```java @EnableGracefulResponse @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 第三步:开发接口 具体的案例可以参考本项目的示例工程,https://github.com/feiniaojin/graceful-response-example.git 特殊情况: (1)controller方法返回void 例如: ```java @RequestMapping("/test0") public void test0(){ logger.info("test0: return void"); } ``` 将会封装为: ```json { "status": { "code": 0, "msg": "ok" }, "payload": {} } ``` (2)自定义异常码与错误提示 见上文 (3)异常别名 见上文 --- 使用过程中如遇到问题,可以联系作者。 公众号: MarkWord 目前正在连载更新《Thinking in DDD》系列文章,未来将会对团队管理、架构方法论进行系列分享,欢迎关注 [![vA1OFU.jpg](https://s1.ax1x.com/2022/08/01/vA1OFU.jpg)](https://imgtu.com/i/vA1OFU) [![vA8Dbt.jpg](https://s1.ax1x.com/2022/08/01/vA8Dbt.jpg)](https://imgtu.com/i/vA8Dbt)