实战篇:通用返回值 & 异常处理设计 | SpringMVC第14篇
共 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 <T> ResultDto<T> doBusException(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 系列
SpringMVC 系列第 1 篇:helloword SpringMVC 系列第 2 篇:@Controller、@RequestMapping SpringMVC 系列第 3 篇:异常高效的一款接口测试利器 SpringMVC 系列第 4 篇:controller 常见的接收参数的方式 SpringMVC 系列第 5 篇:@RequestBody 大解密,说点你不知道的 SpringMVC 系列第 6 篇:上传文件的 4 种方式,你都会么? SpringMVC 系列第 7 篇:SpringMVC 返回视图常见的 5 种方式,你会几种? SpringMVC 系列第 8 篇:返回 json & 通用返回值设计 SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思? SpringMVC 系列第 10 篇:异步处理 SpringMVC 系列第 11 篇:集成静态资源 SpringMVC 系列第 12 篇:拦截器 SpringMVC 系列第 13 篇:统一异常处理
8、更多好文章
Spring 高手系列(共 56 篇) Java 高并发系列(共 34 篇) MySql 高手系列(共 27 篇) Maven 高手系列(共 10 篇) Mybatis 系列(共 12 篇) 聊聊 db 和缓存一致性常见的实现方式 接口幂等性这么重要,它是什么?怎么实现? 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!