实战篇:通用返回值 & 异常处理设计 | SpringMVC第14篇

路人甲Java

共 12357字,需浏览 25分钟

 ·

2021-09-04 14:25

大家好,我是路人,这是 SpringMVC 系列第 14 篇。

1、本文目的

目前多数系统都采用前后端分离的方式,后端只负责提供 restfull 接口,返回 json 格式的数据就可以了,前端负责渲染。

本文带大家主要解决 2 个问题,在 springmvc 提供 json 格式的接口的时候,需要解决 2 个问题

  • 问题 1:所有接口的返回值采用统一的格式
  • 问题 2:系统中异常处理设计的问题,采用一种非常好的方式来解决这个问题

下面咱们一起来解决这 2 个问题。

2、解决问题 1:实现统一的返回值

所有的接口均返回 ResultDto 类型的数据,ResultDto 类的代码如下,主要有 4 个字段

  • success:表示接口是成功还是失败
  • code:错误码,当有异常的时候,可以返回具体的错误码
  • msg:提示信息,比如:操作成功、用户名有误、密码有误等等
  • data:类型是一个泛型,表示任意类型,这个用来存放接口中具体返回的数据,可以是任意类型的对象
  • 还提供了几个静态方法,方便创建 ResultDto 对象
/**
 * rest接口通用返回值数据结构
 *
 * @param <T>
 */

public class ResultDto<T{
    //接口状态(成功还是失败)
    private Boolean success;
    //错误码
    private String code;
    //提示信息
    private String msg;
    //数据
    private T data;

    public Boolean getSuccess() {
        return success;
    }

    public void setSuccess(Boolean success) {
        this.success = success;
    }

    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 T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static <T> ResultDto<T> success(T data) {
        return success(data, "操作成功!");
    }

    public static <T> ResultDto<T> success(T data, String msg) {
        ResultDto<T> result = new ResultDto<>();
        result.setSuccess(Boolean.TRUE);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }

    public static <T> ResultDto<T> error(String msg) {
        return error(null,msg);
    }
    public static <T> ResultDto<T> error(String code,String msg) {
       return error(code,msg,null);
    }
    public static <T> ResultDto<T> error(String code, String msg, T data) {
        ResultDto<T> result = new ResultDto<>();
        result.setSuccess(Boolean.FALSE);
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}

3、解决问题 2:统一处理异常

3.1、如何做?

异常处理这块,我们的设计主要有 2 点,通过这 2 点来解决异常处理的问题

  • 第一点:定义一个基础的业务异常类(BusException),业务代码中手动抛出异常的时候,统一抛出这种类型的异常,异常类型中可以携带更详细的错误信息,比如错误码、提示信息、扩展数据等等
  • 第 2 点:采用 springmvc 全局来处理异常,控制器中不要捕获异常,将一次交给 springmvc 框架来统一处理。

3.2、具体代码

下面我们来看具体的代码片段,主要有 2 个类

业务异常类:BusException

代码比较简单,主要有 2 个属性和几个静态方法

  • code:异常错误码,最终会丢给 ResultDto 的 code 属性输出到客户端
  • data:异常的时候,可以传递一些扩展信息,此时可以丢到 data 中,最终会丢给 ResultDto 的 data 属性输出到客户端
  • 提供了几个静态方法,便于抛出 BusException 异常
  • 当我们的业务代码中需要抛出异常的时候,要求均抛出 BusException 类型的异常
/**
 * 业务异常
 */

public class BusException extends RuntimeException {
    //异常错误码
    private String code;
    //错误扩展信息
    private Object data;

    public BusException(String msg) {
        this(null, msg);
    }

    public BusException(String code, String msg) {
        this(code, msg, null);
    }

    public BusException(String code, String msg, Object data) {
        super(msg);
        this.code = code;
        this.data = data;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static void throwBusException(String msg) {
        throwBusException(null, msg);
    }

    public static void throwBusException(String code, String msg) {
        throwBusException(code, msg, null);
    }

    public static void throwBusException(String code, String msg, Object data) {
        throw new BusException(code, msg, data);
    }
}

全局异常统一处理类:GlobalExceptionHandle

如下代码,大家对 springmvc 统一异常处理不了解,建议先看一下上一篇文章。

注意下面代码中的@1,这里使用到了@RestControllerAdvice,这个注解之前没有介绍过,他和@ControllerAdvice 功能类似,只是这个注解内部定义的时候上面多了一个@ResponseBody 注解,表示下面这个类中处理异常的方法返回值最终都会以 json 格式输出到客户端

/**
 * 全局异常处理
 */

@RestControllerAdvice // @1
public class GlobalExceptionHandle {
    /**
     * 统一处理业务异常
     *
     * @param e
     * @param <T>
     * @return
     */

    @ExceptionHandler(BusException.class)
    public <TResultDto<TdoBusException(BusException e
{
        //1、记录错误日志
        //2、返回结果
        return ResultDto.error(e.getCode(), e.getMessage(), (T) e.getData());
    }

    /**
     * 处理其他异常
     *
     * @param e
     * @param <T>
     * @return
     */

    @ExceptionHandler
    public <T> ResultDto<T> doException(Exception e) {
        //1、记录错误日志
        //2、返回结果
        return ResultDto.error("系统异常,请联系管理员,错误详情:" + e.getMessage());
    }
}

2 个问题解决了,下面我们来看看 controller 中如何使用。

4、Controller 中代码如何写?

来个案例

如下代码,注意两点信息

  • 内部提供了 2 个接口,接口的返回值都是 ResultDto 类型的
  • 代码中,没有了 try catch,而是将异常类型封装为 BusException 类型抛出,比如验证码有误,会抛出了 BusException,顺便携带了错误码和错误提示信息,这些都会通过全局异常的处理,输出到客户端
@RestController
@RequestMapping("/user")
public class UserController {

    /**
     * 获取用户id
     *
     * @param code
     * @return
     */

    @RequestMapping("/getUserName")
    public ResultDto<String> getUserName(@RequestParam("code") Integer code) {
        if (!Integer.valueOf(6666).equals(code)) {
            //验证码有误的时候,返回4001错误码
            BusException.throwBusException("4001""验证码有误!");
        }
        return ResultDto.success("路人");
    }

    /**
     * 获取用户id
     *
     * @param code
     * @return
     */

    @RequestMapping("/getUserId")
    public ResultDto<String> getUserId(@RequestParam("code") Integer code) {
        if (!Integer.valueOf(6666).equals(code)) {
            BusException.throwBusException("4001""验证码有误!");
        }
        return ResultDto.success("8888");
    }

}

验证效果

下面我们通过 idea 提供的 HTTP client 工具搞三个测试用例,测试下接口的效果,如下图,大家可以分别运行一下 3 个案例。

用例 1 输出结果:

{
  "success"true,
  "code"null,
  "msg""操作成功!",
  "data""路人"
}

用例 2 输出结果:

{
  "success"false,
  "code""4001",
  "msg""验证码有误!",
  "data"null
}

用例 3 输出的结果:

{
  "success"false,
  "code"null,
  "msg""系统异常,请联系管理员,错误详情:Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: \"abc\"",
  "data"null
}

5、总结

本文内容主要有 2 点:统一返回值、统一异常的处理,这 2 点大家要好好掌握,目前业界很少使用 springmvc 直接开发接口了,更多的是采用 springboot 来开发接口,本文的内容直接可以用到 springboot 中,来优化咱们的系统。

6、案例代码

git地址:https://gitee.com/javacode2018/springmvc-series

7、SpringMVC 系列

  1. SpringMVC 系列第 1 篇:helloword
  2. SpringMVC 系列第 2 篇:@Controller、@RequestMapping
  3. SpringMVC 系列第 3 篇:异常高效的一款接口测试利器
  4. SpringMVC 系列第 4 篇:controller 常见的接收参数的方式
  5. SpringMVC 系列第 5 篇:@RequestBody 大解密,说点你不知道的
  6. SpringMVC 系列第 6 篇:上传文件的 4 种方式,你都会么?
  7. SpringMVC 系列第 7 篇:SpringMVC 返回视图常见的 5 种方式,你会几种?
  8. SpringMVC 系列第 8 篇:返回 json & 通用返回值设计
  9. SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思?
  10. SpringMVC 系列第 10 篇:异步处理
  11. SpringMVC 系列第 11 篇:集成静态资源
  12. SpringMVC 系列第 12 篇:拦截器
  13. SpringMVC 系列第 13 篇:统一异常处理

8、更多好文章

  1. Spring 高手系列(共 56 篇)
  2. Java 高并发系列(共 34 篇)
  3. MySql 高手系列(共 27 篇)
  4. Maven 高手系列(共 10 篇)
  5. Mybatis 系列(共 12 篇)
  6. 聊聊 db 和缓存一致性常见的实现方式
  7. 接口幂等性这么重要,它是什么?怎么实现?
  8. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
浏览 64
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报