1.前言
在做項目的時候,常常會遇到這些情況,
(一)、繁多的if..else 造成大量的參數代碼判斷,於是就在實體類字段中添加@NotBlank,@NotNull等注解代替,可是注解上自定義的message 消息無法規范的返回到前端;
(二)、在業務層代碼中,當方法層層嵌套,對最深處的代碼進行不滿足的參數做判斷時,直接返回響應體並不是很合適(這個時候就需要拋出自定義異常)
(三)、通過Assert 斷言去除冗余的if..else 時,發現斷言拋出的異常也沒有規范的返回前端;
為了解決這些情況,我們需要做一個統一異常處理,無論是注解的異常拋出,自定義異常拋出、以及斷言的異常拋出都需要統一獲取其中的message。這個統一異常處理即今天所要說的 @ExceptionHandler+@ControllerAdvice ,統一異常處理。
2.直接實戰
原理也不解釋,廢話也不多說,直接記錄怎么配置,怎么使用。想知道原理的,自行百度。
2.1.創建項目
通過IDEA 中的SpringInitializr 創建springboot項目,並一次創建各個層的文件夾,controller、service、entity、model、exception、handler等文件夾。如下所示:
2.2 統一異常配置
pom.xml 添加 以下依賴
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
2.2.1 Result 響應實體類,用來返回前端的實體類。
/**
* @Author: DZBiao
* @Date : 2021/12/11
* @Description : 描述:
**/
public class Result {
/**
* 響應狀態碼
*/
private Integer code;
/**
* 響應成功與否
*/
private boolean success;
/**
* 響應消息
*/
private String msg;
/**
* 響應數據
*/
private Object data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Result() {
}
public Result(Integer code, boolean success, String msg) {
this.code = code;
this.success = success;
this.msg = msg;
}
public Result(Integer code, boolean success, String msg, Object data) {
this.code = code;
this.success = success;
this.msg = msg;
this.data = data;
}
/**
* 成功 返回默認成功信息
*
* @return
*/
public static Result SUCCESS() {
return new Result(1, true, "操作成功", null);
}
/**
* 成功 返回(data數據)成功信息
*
* @param data
* @return
*/
public static Result SUCCESS(Object data) {
return new Result(1, true, "操作成功", data);
}
/**
* 成功 返回自定義(消息、data數據)成功信息
*
* @param msg
* @param data
* @return
*/
public static Result SUCCESS(String msg, Object data) {
return new Result(1, true, msg, data);
}
/**
* 失敗 返回默認失敗信息
*
* @return
*/
public static Result ERROR() {
return new Result(-1, false, "操作失敗", null);
}
/**
* 失敗 返回自定義(消息)失敗信息
*
* @param msg
* @return
*/
public static Result ERROR(String msg) {
return new Result(-1, false, msg, null);
}
/**
* 失敗 返回自定義(消息、狀態碼)失敗信息
*
* @param code
* @param msg
* @return
*/
public static Result ERROR(Integer code, String msg) {
return new Result(code, false, msg, null);
}
}
2.2.2 @Controller 和 @RestControllerAdvice 配置
在controller層中新建IndexController類,傳參實體類使用簡單的User類;
@RestController
@RequestMapping("/index")
public class IndexController {
@PostMapping("/list")
public Result index(@Valid @RequestBody User user) throws Exception {
return Result.SUCCESS();
}
}
實體類User ,當我想要在傳參時進行非空判斷,則添加@NotBlank注解,並在Controller層 添加@Valid和@RequestBody 注解。(@Data 注解為Lombok 插件,idea中安裝,如果沒有直接在實體類中生成get和set 方法代替.)
@Data
public class User {
private String id ; // ID
@NotBlank(message = "用戶名不能為空.")
private String username ; // 用戶名
@NotBlank(message = "性別不能為空")
private String sex ; // 性別
@NotBlank(message = "地址不能為空")
private String address ; // 地址
private String status ;
}
在handler 文件夾中新建GlobalExceptionController類,並配置@RestControllerAdvice注解。對於實體類參數上的注解的異常捕獲,我們在統一異常處理類中,通過MethodArgumentNotValidException進行捕獲處理。
@RestControllerAdvice
public class GlobalExceptionController {
/**
* 處理方法參數異常,如 實體類上的@NotBlank注解異常,@NotNull注解等異常信息返回。
* @param ex
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleMethodArgumentException(MethodArgumentNotValidException ex){
return Result.ERROR(ex.getBindingResult().getFieldError().getDefaultMessage()) ;
}
}
至此我們解決了三個問題中的其中一個問題。下面解決拋出自定義異常 和斷言異常的捕獲。
在GlobalExceptionController中再添加 處理自定義異常和斷言異常的捕獲方法。
@RestControllerAdvice
public class GlobalExceptionController {
/**
* 處理方法參數異常,如 實體類上的@NotBlank注解異常,@NotNull注解等異常信息返回。
* @param ex
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleMethodArgumentException(MethodArgumentNotValidException ex){
return Result.ERROR(ex.getBindingResult().getFieldError().getDefaultMessage()) ;
}
/**
* 斷言、自定義異常等處理
* @param ex
* @return
*/
@ExceptionHandler(Exception.class)
public Result handleException(Exception ex){
return Result.ERROR(ex.getMessage()) ;
}
}
3.測試
我們在exception文件夾中創建自定義異常類:CustomException,並繼承RuntimeException
public class CustomException extends RuntimeException {
private String message ;
public CustomException(String message){
this.message = message ;
}
@Override
public String getMessage() {
return message;
}
public CustomException setMessage(String message) {
this.message = message;
return this;
}
}
在controller中添加一些判斷進行觀察,看看我們配置的情況。
@RestController
@RequestMapping("/index")
public class IndexController {
@PostMapping("/list")
public Result index(@Valid @RequestBody User user) throws Exception {
// 拋出Exception異常
if (user.getUsername().equals("xxx")){
throw new Exception("用戶名錯誤.");
}
// 自定義異常
if (user.getUsername().equals("xxxxx")){
throw new CustomException("自定義異常");
}
// 斷言異常
Assert.notNull(user.getStatus(), "狀態不能為空");
return Result.SUCCESS();
}
}
我們通過Postman或者ApiPost觀察。當我們什么參數都不傳時,就會捕獲注解上的異常message,當username傳“xxx”,就會捕獲用戶名錯誤.異常,同理,可以觀察到其他的情況,不多贅述。
統一異常處理配置很簡單。至此配置完成。
通過統一異常處理的配置,我們可以去除大量的if..else 冗余代碼,全部交由注解或者Assert斷言,或者自定義異常來解決。
需要注意的一點是:該@Valid 注解 和@NotBlank 注解 是在springboot3.6以下版本基礎之上的。本地demo使用的版本是2.6.1,springboot3.6 需要使用 @Validated 注解 。