V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
feiniaojin
V2EX  ›  程序员

分享一个优雅处理 Spring Boot 接口响应的 Java 库,提供一站式统一返回值封装、全局异常处理、自定义异常错误码等功能,求 star

  •  1
     
  •   feiniaojin · 2023-11-23 00:15:59 +08:00 · 767 次点击
    这是一个创建于 374 天前的主题,其中的信息可能已经有所发展或是发生改变。

    简介

    Graceful Response 是一个 Spring Boot 体系下的优雅响应处理器,提供一站式统一返回值封装、全局异常处理、自定义异常错误码等功能,使用 Graceful Response 进行 web 接口开发不仅可以节省大量的时间,还可以提高代码质量,使代码逻辑更清晰。

    零学习门槛,十秒钟上手!

    GitHub 仓库地址: https://github.com/feiniaojin/graceful-response

    欢迎试用和 star!

    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 中央仓库,我们可以直接引入到项目中。

    maven 依赖如下:

    <dependency>
        <groupId>com.feiniaojin</groupId>
        <artifactId>graceful-response</artifactId>
        <version>{latest.version}</version>
    </dependency>
    
    Spring Boot 版本 Graceful Response 版本
    2.x 3.2.0-boot2
    3.x 3.2.0-boot3

    启用 Graceful Response

    在启动类中引入 @EnableGracefulResponse 注解,即可启用 Graceful Response 组件。

    @EnableGracefulResponse
    @SpringBootApplication
    public class ExampleApplication {
        public static void main(String[] args) {
            SpringApplication.run(ExampleApplication.class, args);
        }
    }
    

    Controller 层

    引入 Graceful Response 后,我们不需要再手工进行查询结果的封装,直接返回实际结果即可,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"
      }
    }
    

    而对于命令操作( Command )尽量不返回数据,因此 command 操作的方法的返回值应该是 void ,Graceful Response 对于对于返回值类型 void 的方法,也会自动进行封装。

    public class Controller {
        @RequestMapping("/command")
        @ResponseBody
        public void command() {
            //业务操作
        }
    }
    

    成功调用该接口,将得到:

    {
      "status": {
        "code": "200",
        "msg": "success"
      },
      "payload": {}
    }
    

    Service 层

    在引入 Graceful Response 前,有的开发者在定义 Service 层的方法时,为了在接口中返回异常码,干脆直接将 Service 层方法定义为 Response ,淹没了方法的正常返回值。

    Response 的代码如下。

    //lombok 注解
    @Data
    public class Response {
        private String code;
        private String msg;
        private Object data;
    }
    

    直接返回 Response 的 Service 层方法:

    /**
     * 直接返回 Reponse 的 Service
     * 不规范
     */
    public interface Service{
        public Reponse commandMethod(Command command);
    }
    

    Graceful Response 引入 @ExceptionMapper 注解,通过该注解将异常和错误码关联起来,这样 Service 方法就不需要再维护 Response 的响应码了,直接抛出业务异常,由 Graceful Response 进行异常和响应码的关联。 @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;
        public UserInfoView queryOne(Query query) {
            UserInfo userInfo = mapper.findOne(query.getId());
            if (Objects.isNull(userInfo)) {
                //这里直接抛自定义异常
                throw new NotFoundException();
            }
            //……后续业务操作
        }
    }
    

    当 Service 层的 queryOne 方法抛出 NotFoundException 时,Graceful Response 会进行异常捕获,并将 NotFoundException 对应的异常码和异常信息封装到统一的响应对象中,最终接口返回以下 JSON 。

    {
      "status": {
        "code": "1404",
        "msg": "找不到对象"
      },
      "payload": {}
    }
    

    参数校验

    Graceful Response 对 JSR-303 数据校验规范和 Hibernate Validator 进行了增强,Graceful Response 自身不提供参数校验的功能,但是用户使用了 Hibernate Validator 后,Graceful Response 可以通过 @ValidationStatusCode 注解为参数校验结果提供响应码,并将其统一封装返回。

    例如以下的 UserInfoQuery 。

    @Data
    public class UserInfoQuery {
        @NotNull(message = "userName is null !")
        @Length(min = 6, max = 12)
        @ValidationStatusCode(code = "520")
        private String userName;
    }
    

    UserInfoQuery 对象中定义了 @NotNull 和 @Length 两个校验规则,在未引入 Graceful Response 的情况下,会直接抛出异常;

    在引入 Graceful Response 但是没有加入 @ValidationStatusCode 注解的情况下,会以默认的错误码进行返回;

    在上面的 UserInfoQuery 中由于使用了 @ValidationStatusCode 注解,并指定异常码为 520 ,则当 userName 字段任意校验不通过时,都会使用异常码 520 进行返回,如下。

    {
      "status": {
        "code": "520",
        "msg": "userName is null !"
      },
      "payload": {}
    }
    

    而对于 Controller 层直接校验方法入参的场景,Graceful Response 也进行了增强,如以下 Cntroller 。

    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 也支持用户自定义响应体,关于自定义响应体的技术实现,请到 https://github.com/feiniaojin/graceful-response 进行了解。

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1606 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 16:58 · PVG 00:58 · LAX 08:58 · JFK 11:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.