前言
本篇文章主要介紹的是SpringBoot的全局異常處理。
GitHub源碼鏈接位於文章底部。
首先還是來看工程的結構

在pom文件中添加相關依賴
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!--父級依賴,它用來提供相關的 Maven 默認依賴。使用它之后,常用的springboot包依賴可以省去version 標簽-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath ></relativePath>
</parent>
<dependencies>
<!-- Spring Boot Web 依賴 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok插件依賴-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
</dependencies>
編碼
Spring Boot的全局異常處理有兩個很重要的注解,一個是ControllerAdvice,一個是ExceptionHandler,我們在類上使用ControllerAdvice注解,表示開啟了全局異常的捕獲;在方法上使用ExceptionHandler注解去捕獲具體某一個異常,然后再該方法體內對捕獲到的異常進行處理,進行輸出、返回等操作。我們來看看下面這個示例是怎么操作的:
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(value =NullPointerException.class)
public String exceptionHandler(NullPointerException e){
System.out.println("發生空指針異常:"+e);
return e.getMessage();
}
@ExceptionHandler(value = ArithmeticException.class)
public String exceptionHandler(ArithmeticException e) {
System.out.println("發生數學運算異常:"+e);
return e.getMessage();
}
}
上面這個類中就全局捕獲了兩種具體的異常,我們可以不斷地往這個類中添加一些異常。在實際運用過程中,我們會對方法體中捕獲的異常進行二次處理,封裝成需要的格式,比如一些通用的錯誤碼,返回消息,返回標識等等,這里通過自定義的異常類以及枚舉類來實現。
自定義枚舉類
首先自定義一個枚舉類,該枚舉類中枚舉一些數據操作錯誤定義。如果有需要,后期可以不斷地增加對應枚舉情況。
public enum CommonEnum {
// 數據操作錯誤定義
SUCCESS(true,200, "請求成功!"),
ERROR(false,201, "請求失敗!"),
BODY_NOT_MATCH(false,400,"請求的參數錯誤!"),
SIGNATURE_NOT_MATCH(false,401,"請求的數字簽名不匹配!"),
NOT_FOUND(false,404, "未找到該資源!"),
INTERNAL_SERVER_ERROR(false,500, "服務器內部錯誤!"),
SERVER_BUSY(false,503,"服務器正忙,請稍后再試!");
/** 錯誤碼 */
private Integer resultCode;
/** 錯誤描述 */
private String resultMsg;
private Boolean flag;
CommonEnum(Boolean flag, Integer resultCode, String resultMsg) {
this.resultCode = resultCode;
this.resultMsg = resultMsg;
this.flag = flag;
}
/** 返回結果集中用到get方法*/
public Boolean getFlag() {
return flag;
}
public Integer getResultCode() {
return resultCode;
}
public String getResultMsg() {
return resultMsg;
}
}
自定義一個異常類,用於處理發生的業務異常
@Data
public class BizException extends RuntimeException {
private static final long serialVersionUID = 1L;
/** 錯誤碼*/
protected Integer errorCode;
/** 錯誤信息*/
protected String errorMsg;
public BizException(Integer errorCode, String errorMsg) {
super(errorCode.toString());
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
@Override
public String getMessage() {
return errorMsg;
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}
自定義數據格式
定義返回的結果集的格式,也就是改造Result類,和枚舉類結合起來
@Data
public class Result {
/**
* 返回結構狀態
*/
private Boolean flag;
/**
* 返回狀態碼
*/
private Integer code;
/**
* 返回消息
*/
private String message;
/**
* 返回數據內容
*/
private Object data;
public Result(CommonEnum commonEnum, Object data) {
this.flag = commonEnum.getFlag();
this.code = commonEnum.getResultCode();
this.message = commonEnum.getResultMsg();
this.data = data;
}
public Result(CommonEnum commonEnum) {
this.flag = commonEnum.getFlag();
this.code = commonEnum.getResultCode();
this.message = commonEnum.getResultMsg();
}
/**
* 自定義異常
*/
public Result(Integer code, String message) {
this.flag = false;
this.code = code;
this.message = message;
}
}
自定義全局異常處理類
最后來自定義全局異常處理類,可以處理自己拋出的自定義的業務異常,也可以處理系統異常,如空指針等,或者一些其他異常。其實就是第一個例子的延伸。
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 處理自定義的業務異常
* @param e
*/
@ExceptionHandler(value = BizException.class)
@ResponseBody
public Result bizExceptionHandler(BizException e) {
logger.error("發生業務異常!原因是:{}", e.getErrorMsg());
return new Result(e.getErrorCode(), e.getErrorMsg());
}
/**
* 處理空指針的異常
* @param e
*/
@ExceptionHandler(value = NullPointerException.class)
@ResponseBody
public Result exceptionHandler(NullPointerException e) {
logger.error("發生空指針異常!原因是:", e);
return new Result(CommonEnum.BODY_NOT_MATCH,"空指針異常");
}
/**
* 格式轉換異常
* @param e
*/
@ExceptionHandler(value = ParseException.class)
@ResponseBody
public Result exceptionHandler(ParseException e) {
logger.error("發生格式轉換異常!原因是:", e);
return new Result(CommonEnum.BODY_NOT_MATCH,"格式轉換錯誤");
}
/**
* 方法安全權限異常
* @param e
*/
@ExceptionHandler(value = IllegalAccessException.class)
@ResponseBody
public Result exceptionHandler(IllegalAccessException e) {
logger.error("發生方法安全權限異常!原因是反射了private方法:", e);
return new Result(CommonEnum.INTERNAL_SERVER_ERROR,"方法安全權限異常!不能反射private方法");
}
/**
* 數學運算異常
* @param e
* @return
*/
@ExceptionHandler(value = ArithmeticException.class)
@ResponseBody
public Result exceptionHandler(ArithmeticException e) {
logger.error("發生數學運算異常:", e);
return new Result(CommonEnum.INTERNAL_SERVER_ERROR,"數學運算異常");
}
/**
* 處理其他異常
* @param e
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result exceptionHandler(Exception e) {
logger.error("未知異常!原因是:", e);
return new Result(CommonEnum.INTERNAL_SERVER_ERROR,未知異常!);
}
}
logger.error是輸出語句,為了在控制台看到結果,等同於System.out,等同於e.printStackTrace()。
可以看到,這里返回的是之前自定義的結果集,該結果集里定義了多個構造方法,比如new Result(枚舉,Obj),new Result(枚舉)以及其他,如果需要,可再自行增加。此外,其他遇到的異常也可以在這里按照格式增加。
創建User實體類
因為這里不連接數據庫,所以只定義簡單的屬性,pom文件中也沒有添加數據庫依賴。
@Data
public class User implements Serializable {
/** id */
private String id;
/** 姓名 */
private String name;
/** 年齡 */
private Integer age;
}
UserController控制層
控制層也使用不同的請求方式,不一樣的是,這里請求方式和具體邏輯沒有關系,只是為了方便操作api,同時在各個api中故意制造了一些異常,進行測試用,可以參考代碼注釋。
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping
public Result insert(@RequestBody User user) {
if (user.getName() == null) {
//拋出一個自定義異常
throw new BizException(-1, "用戶名不能為空!!!");
}
//如果沒有異常,則返回數據為上傳的user
return new Result(CommonEnum.SUCCESS,user);
}
@DeleteMapping
public Result delete() {
//制造一個空指針異常,並且不進行處理,會被全局捕獲
String str=null;
str.equals("test");
//如果沒有異常,則返回請求成功
return new Result(CommonEnum.SUCCESS);
}
@PutMapping
public Result update() {
//這里故意造成數字異常,並且不進行處理
int i = 1 / 0;
//如果沒有異常,則返回請求成功,返回數據為i
return new Result(CommonEnum.SUCCESS,i);
}
@GetMapping
public Result find() {
/**
* 這里故意造成索引越界異常,並且不進行處理,
* 因為沒有進行處理,全局異常中也沒有進行具體異常的捕獲
* 所以被最后的Exception捕獲了
*/
List<String> list = new ArrayList<>();
String str = list.get(0);
//如果沒有異常,則返回請求成功,返回數據為str
return new Result(CommonEnum.SUCCESS,str);
}
}
程序入口
@SpringBootApplication
public class ExceptionHandlerApplication {
public static void main(String[] args) {
SpringApplication.run(ExceptionHandlerApplication.class, args);
}
}
配置文件
在application.yml中設置訪問端口
server:
port: 8080
測試
在postman中調用接口測試
捕獲自定義的業務異常(post方法)

這里沒有傳入name參數,所以捕獲了自己拋出的自定義異常
在controller中的post方法中throw new BizException(-1, "用戶名不能為空!!!");,可以看到輸出了我們封裝的返回結果。這里的錯誤碼和錯誤信息可以自己修改。

在傳入name參數后,沒有產生異常,自然也無從捕獲,此時返回的結果集正常為傳入的user對象,id和age沒有傳入,因此為null。
捕獲系統異常(delete、put方法)


因為沒有傳入參數,這里的body就不截出來了。
可以看到,這里的flag,code,message,data的值其實都是全局異常類中封裝的結果集,前三項是枚舉類中的項。如delete方法出現空指針異常,返回的是new Result(CommonEnum.BODY_NOT_MATCH,"空指針異常");這里的CommonEnum.BODY_NOT_MATCH定義了flag,code,message的值,data就是"空指針異常"。
可以在枚舉類中增加NullPointer(false,203,"空指針異常"),然后在全局異常中返回new Result(CommonEnum.NullPointer,"空指針異常"),返回的結果集就是
{
"flag": false,
"code": 203,
"message": "空指針異常",
"data": "空指針異常"
}
put方法同理。
捕獲未在全局異常類中定義的其他異常(get方法)

這里是捕獲了索引越界異常,但是因為全局異常類中沒有捕獲該具體異常,因此被最后一個Exception異常捕獲到了。
因為這里使用了logger.error進行輸出,因此可以在控制台中看到該具體異常,將其按照上面的格式加入到全局異常處理類中,下次再遇到這種異常就能順利捕獲了。
本文GitHub源碼:https://github.com/lixianguo5097/springboot/tree/master/springboot-exceptionHandler
CSDN:https://blog.csdn.net/qq_27682773
簡書:https://www.jianshu.com/u/e99381e6886e
博客園:https://www.cnblogs.com/lixianguo
個人博客:https://www.lxgblog.com
