實(shí)戰(zhàn)篇:通用返回值 & 異常處理設(shè)計(jì) | SpringMVC第14篇
大家好,我是路人,這是 SpringMVC 系列第 14 篇。
1、本文目的
目前多數(shù)系統(tǒng)都采用前后端分離的方式,后端只負(fù)責(zé)提供 restfull 接口,返回 json 格式的數(shù)據(jù)就可以了,前端負(fù)責(zé)渲染。
本文帶大家主要解決 2 個(gè)問題,在 springmvc 提供 json 格式的接口的時(shí)候,需要解決 2 個(gè)問題
問題 1:所有接口的返回值采用統(tǒng)一的格式 問題 2:系統(tǒng)中異常處理設(shè)計(jì)的問題,采用一種非常好的方式來解決這個(gè)問題
下面咱們一起來解決這 2 個(gè)問題。
2、解決問題 1:實(shí)現(xiàn)統(tǒng)一的返回值
所有的接口均返回 ResultDto 類型的數(shù)據(jù),ResultDto 類的代碼如下,主要有 4 個(gè)字段
success:表示接口是成功還是失敗 code:錯(cuò)誤碼,當(dāng)有異常的時(shí)候,可以返回具體的錯(cuò)誤碼 msg:提示信息,比如:操作成功、用戶名有誤、密碼有誤等等 data:類型是一個(gè)泛型,表示任意類型,這個(gè)用來存放接口中具體返回的數(shù)據(jù),可以是任意類型的對(duì)象 還提供了幾個(gè)靜態(tài)方法,方便創(chuàng)建 ResultDto 對(duì)象
/**
* rest接口通用返回值數(shù)據(jù)結(jié)構(gòu)
*
* @param <T>
*/
public class ResultDto<T> {
//接口狀態(tài)(成功還是失?。?/span>
private Boolean success;
//錯(cuò)誤碼
private String code;
//提示信息
private String msg;
//數(shù)據(jù)
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:統(tǒng)一處理異常
3.1、如何做?
異常處理這塊,我們的設(shè)計(jì)主要有 2 點(diǎn),通過這 2 點(diǎn)來解決異常處理的問題
第一點(diǎn):定義一個(gè)基礎(chǔ)的業(yè)務(wù)異常類(BusException),業(yè)務(wù)代碼中手動(dòng)拋出異常的時(shí)候,統(tǒng)一拋出這種類型的異常,異常類型中可以攜帶更詳細(xì)的錯(cuò)誤信息,比如錯(cuò)誤碼、提示信息、擴(kuò)展數(shù)據(jù)等等 第 2 點(diǎn):采用 springmvc 全局來處理異常,控制器中不要捕獲異常,將一次交給 springmvc 框架來統(tǒng)一處理。
3.2、具體代碼
下面我們來看具體的代碼片段,主要有 2 個(gè)類
業(yè)務(wù)異常類:BusException
代碼比較簡單,主要有 2 個(gè)屬性和幾個(gè)靜態(tài)方法
code:異常錯(cuò)誤碼,最終會(huì)丟給 ResultDto 的 code 屬性輸出到客戶端 data:異常的時(shí)候,可以傳遞一些擴(kuò)展信息,此時(shí)可以丟到 data 中,最終會(huì)丟給 ResultDto 的 data 屬性輸出到客戶端 提供了幾個(gè)靜態(tài)方法,便于拋出 BusException 異常 當(dāng)我們的業(yè)務(wù)代碼中需要拋出異常的時(shí)候,要求均拋出 BusException 類型的異常
/**
* 業(yè)務(wù)異常
*/
public class BusException extends RuntimeException {
//異常錯(cuò)誤碼
private String code;
//錯(cuò)誤擴(kuò)展信息
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);
}
}
全局異常統(tǒng)一處理類:GlobalExceptionHandle
如下代碼,大家對(duì) springmvc 統(tǒng)一異常處理不了解,建議先看一下上一篇文章。
注意下面代碼中的
@1,這里使用到了@RestControllerAdvice,這個(gè)注解之前沒有介紹過,他和@ControllerAdvice 功能類似,只是這個(gè)注解內(nèi)部定義的時(shí)候上面多了一個(gè)@ResponseBody 注解,表示下面這個(gè)類中處理異常的方法返回值最終都會(huì)以 json 格式輸出到客戶端
/**
* 全局異常處理
*/
@RestControllerAdvice // @1
public class GlobalExceptionHandle {
/**
* 統(tǒng)一處理業(yè)務(wù)異常
*
* @param e
* @param <T>
* @return
*/
@ExceptionHandler(BusException.class)
public <T> ResultDto<T> doBusException(BusException e) {
//1、記錄錯(cuò)誤日志
//2、返回結(jié)果
return ResultDto.error(e.getCode(), e.getMessage(), (T) e.getData());
}
/**
* 處理其他異常
*
* @param e
* @param <T>
* @return
*/
@ExceptionHandler
public <T> ResultDto<T> doException(Exception e) {
//1、記錄錯(cuò)誤日志
//2、返回結(jié)果
return ResultDto.error("系統(tǒng)異常,請(qǐng)聯(lián)系管理員,錯(cuò)誤詳情:" + e.getMessage());
}
}
2 個(gè)問題解決了,下面我們來看看 controller 中如何使用。
4、Controller 中代碼如何寫?
來個(gè)案例
如下代碼,注意兩點(diǎn)信息
內(nèi)部提供了 2 個(gè)接口,接口的返回值都是 ResultDto 類型的 代碼中,沒有了 try catch,而是將異常類型封裝為 BusException 類型拋出,比如驗(yàn)證碼有誤,會(huì)拋出了 BusException,順便攜帶了錯(cuò)誤碼和錯(cuò)誤提示信息,這些都會(huì)通過全局異常的處理,輸出到客戶端
@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)) {
//驗(yàn)證碼有誤的時(shí)候,返回4001錯(cuò)誤碼
BusException.throwBusException("4001", "驗(yàn)證碼有誤!");
}
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", "驗(yàn)證碼有誤!");
}
return ResultDto.success("8888");
}
}
驗(yàn)證效果
下面我們通過 idea 提供的 HTTP client 工具搞三個(gè)測(cè)試用例,測(cè)試下接口的效果,如下圖,大家可以分別運(yùn)行一下 3 個(gè)案例。

用例 1 輸出結(jié)果:
{
"success": true,
"code": null,
"msg": "操作成功!",
"data": "路人"
}
用例 2 輸出結(jié)果:
{
"success": false,
"code": "4001",
"msg": "驗(yàn)證碼有誤!",
"data": null
}
用例 3 輸出的結(jié)果:
{
"success": false,
"code": null,
"msg": "系統(tǒng)異常,請(qǐng)聯(lián)系管理員,錯(cuò)誤詳情: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、總結(jié)
本文內(nèi)容主要有 2 點(diǎn):統(tǒng)一返回值、統(tǒng)一異常的處理,這 2 點(diǎn)大家要好好掌握,目前業(yè)界很少使用 springmvc 直接開發(fā)接口了,更多的是采用 springboot 來開發(fā)接口,本文的內(nèi)容直接可以用到 springboot 中,來優(yōu)化咱們的系統(tǒng)。
6、案例代碼
git地址:https://gitee.com/javacode2018/springmvc-series

7、SpringMVC 系列
SpringMVC 系列第 1 篇:helloword SpringMVC 系列第 2 篇:@Controller、@RequestMapping SpringMVC 系列第 3 篇:異常高效的一款接口測(cè)試?yán)?/a> SpringMVC 系列第 4 篇:controller 常見的接收參數(shù)的方式 SpringMVC 系列第 5 篇:@RequestBody 大解密,說點(diǎn)你不知道的 SpringMVC 系列第 6 篇:上傳文件的 4 種方式,你都會(huì)么? SpringMVC 系列第 7 篇:SpringMVC 返回視圖常見的 5 種方式,你會(huì)幾種? SpringMVC 系列第 8 篇:返回 json & 通用返回值設(shè)計(jì) SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思? SpringMVC 系列第 10 篇:異步處理 SpringMVC 系列第 11 篇:集成靜態(tài)資源 SpringMVC 系列第 12 篇:攔截器 SpringMVC 系列第 13 篇:統(tǒng)一異常處理
8、更多好文章
Spring 高手系列(共 56 篇) Java 高并發(fā)系列(共 34 篇) MySql 高手系列(共 27 篇) Maven 高手系列(共 10 篇) Mybatis 系列(共 12 篇) 聊聊 db 和緩存一致性常見的實(shí)現(xiàn)方式 接口冪等性這么重要,它是什么?怎么實(shí)現(xiàn)? 泛型,有點(diǎn)難度,會(huì)讓很多人懵逼,那是因?yàn)槟銢]有看這篇文章!
