先創建一個crud的項目。
controller調用service調用mapper
以下以簡單代碼代替
- controller
@GetMapping("/getUserById")
public String getUserById(String id){
String userById = userService.getUserById(id);
return userById;
}
- service
@Override
public String getUserById(String id) {
// 模擬業務
User user = userMapper.selectById(id);
return user.toString();
}
上面代碼雖然是把數據返回給前台了,但是沒有處理異常以及沒有一個明確的標識告訴前端我是成功還是失敗了,此時我們需要封裝一下統一的成功失敗標志來告訴前台如何處理返回數據。(lombok的使用要綜合考慮,不要因為編碼一時爽引入導致后期Java版本升級困難)
使用Lombok 簡單封裝了一個CommonResponse類
@Data
public class CommonResponse {
/**
* 返回業務碼用來判斷成功失敗
* 200 成功
* 500 失敗
*/
private String code;
/** 描述 */
private String massage;
/** 描述 */
private Object date;
public CommonResponse(String code, String massage, Object date) {
this.code = code;
this.massage = massage;
this.date = date;
}
public static CommonResponse succeed(){
return getCommonResponse(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getMassage(), null);
}
public static CommonResponse succeed(Object date){
return getCommonResponse(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getMassage(), date);
}
public static CommonResponse succeed(String massage,Object date){
return getCommonResponse(CodeEnum.SUCCESS.getCode(), massage, date);
}
public static CommonResponse error(String massage){
return getCommonResponse(CodeEnum.ERROR.getCode(), massage, null);
}
public static CommonResponse error(String code,String massage){
return getCommonResponse(code, massage, null);
}
public static CommonResponse error(){
return getCommonResponse(CodeEnum.ERROR.getCode(), CodeEnum.ERROR.getMassage(), null);
}
public static CommonResponse getCommonResponse(String code, String massage, Object date){
return new CommonResponse(code,massage,date);
}
}
返回的controller使用統一的CommonResponse
@GetMapping("/getUserById")
public CommonResponse getUserById(String id){
String userById = userService.getUserById(id);
return CommonResponse.succeed(userById);
}
返回
{
"code": "200",
"massage": "成功",
"date": "User(id=1, username=嘉文00, password=1000000, age=5555)"
}
上述返回基本符合預期,但是當程序出現未知異常怎么辦了。
對service改造下
@Override
public String getUserByIdException(String id) {
User user = userMapper.selectById(id);
// 模擬業務異常
int i=5/0;
return user.toString();
}
controller 改造
@GetMapping("/getUserById")
public CommonResponse getUserById(String id){
try{
String userById = userService.getUserById(id);
return CommonResponse.succeed(userById);
}catch(Exception e){
e.printStackTrace();
log.error(e.getMessage());
return CommonResponse.error(e.getMessage());
}
}
上面是不是也可以,通過trycatch來判斷如何返回。但是這樣代碼中就會出現大量的try catch,是不是很不好看,我們能不能添加一個統一的try呢,答案是可以的。
使用spring提供的統一的異常處理
spring 提供了三種異常捕獲方式,個人比較推薦這一種
@Slf4j
@ControllerAdvice
public class ExceptionHandle {
/**
* 處理未知異常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public CommonResponse handleException(Exception e){
log.error("系統異常:{}",e.getMessage());
return CommonResponse.error(e.getMessage());
}
/**
* 處理主動拋出的自定義異常
* @param e
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
public CommonResponse handleBusinessException(BusinessException e){
log.error("自定義異常:{}",e.getErrMassage());
return CommonResponse.error(e.getErrCode(),e.getErrMassage());
}
}
踩過的坑:
- 這個地方在寫的時候遇到一個坑,因為捕獲異常后的返回值是CommonResponse,所以要加上注解 @ResponseBody 便於 格式轉換。
- 在配置@ExceptionHandler的時候不能配置兩個相同的Exception。否則會不知道使用哪個而報錯。
這時controller 已經很清晰了,如下:只用處理好業務調用,無需處理向上拋出的異常。
@GetMapping("/getUserByIdException") public CommonResponse getUserByIdException(String id){ String userById = userService.getUserByIdException(id); return CommonResponse.succeed(userById); } @GetMapping("/getUserByIdBusinessException") public CommonResponse getUserByIdBusinessException(String id){ String userById = userService.getUserByIdBusinessException(id); return CommonResponse.succeed(userById); }
當然有時候我們會遇到自己的校驗不通過來終止程序。我們可以throw 一個Exception 或者我們需要定制返回碼,自定義一個異常類也行。如下簡單示例,大家可以根據自己的業務需求去自定義。
- 自定義異常類BusinessException
@Datapublic class BusinessException extends RuntimeException{ private static final long serialVersionUID = 918204099850898995L; private String errCode; private String errMassage; public BusinessException(String errCode,String errMassage){ super(errMassage); this.errCode = errCode; this.errMassage = errMassage; }}
- service返回自定義異常
@Override public String getUserByIdBusinessException(String id) { User user = userMapper.selectById(id); // 模擬業務異常 if("1".equals(id)){ throw new BusinessException("400","id為1的數據不支持查詢。"); } return user.toString(); }
此時我們前端得到的返回
## 請求http://localhost:8088/getUserByIdBusinessException?id=1## 返回{ "code": "400", "massage": "id為1的數據不支持查詢。", "date": null}
以上就是統一異常捕獲跟統一的返回。
另外我們實際項目為了使業務異常好看,統一,我們可以定義一個枚舉來存放我們的業務異常信息。
- 定義一個枚舉類
public enum ExceptionEnum { BUSINESS_NOT_ONE("400","id為1的數據不支持查詢"), ERR_NOT_LOOK("401","長得太帥不讓看") // 往后面累加... ; private String code; private String desc; ExceptionEnum(String code, String desc) { this.code = code; this.desc = desc; } public void ThrowException(){ ThrowException(code,desc); } public void ThrowException(String errMassage){ errMassage = desc +":"+errMassage; ThrowException(code,errMassage); } private BusinessException ThrowException(String code,String desc){ throw new BusinessException(code,desc); }}
- 在service 中拋出枚舉異常
@Override public String getUserByIdBusinessExceptionByEnumOne(String id) { User user = userMapper.selectById(id); // 模擬業務異常 if("1".equals(id)){ ExceptionEnum.BUSINESS_NOT_ONE.ThrowException(); } return user.toString(); } @Override public String getUserByIdBusinessExceptionByEnumTwo(String id) { User user = userMapper.selectById(id); // 模擬業務異常 if("look".equals(id)){ // 可以動態拼接異常信息 ExceptionEnum.ERR_NOT_LOOK.ThrowException("你說對吧"+id); } return user.toString(); }
- 前台返回
{ "code": "400", "massage": "id為1的數據不支持查詢", "date": null}{ "code": "401", "massage": "長得太帥不讓看:你說對吧look", "date": null}
這種做法的好處就是方便管理,一眼就知道自己項目中有多少錯誤,多少異常。但是也會有同學覺得這樣寫好費勁,每次拋異常的時候還要先在枚舉類中定義。所以這個做法是項目跟成員而定。
唯有面對風雨,才能面對未來和時光