Spring Boot全局異常處理,這樣寫(xiě)才優(yōu)雅...
SpringBoot全局異常準(zhǔn)備
說(shuō)明:如果想直接獲取工程那么可以直接跳到底部,通過(guò)鏈接下載工程代碼。
開(kāi)發(fā)準(zhǔn)備
環(huán)境要求:
JDK:1.8
SpringBoot:1.5.17.RELEASE
首先還是Maven的相關(guān)依賴(lài):
- <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>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.17.RELEASE</version>
- <relativePath />
- </parent>
- <dependencies>
- <!-- Spring Boot Web 依賴(lài) 核心 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!-- Spring Boot Test 依賴(lài) -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.41</version>
- </dependency>
- </dependencies>
配置文件這塊基本不需要更改,全局異常的處理只需在代碼中實(shí)現(xiàn)即可。
代碼編寫(xiě)
SpringBoot的項(xiàng)目已經(jīng)對(duì)有一定的異常處理了,但是對(duì)于我們開(kāi)發(fā)者而言可能就不太合適了,因此我們需要對(duì)這些異常進(jìn)行統(tǒng)一的捕獲并處理。
SpringBoot中有一個(gè)ControllerAdvice的注解,使用該注解表示開(kāi)啟了全局異常的捕獲,我們只需在自定義一個(gè)方法使用ExceptionHandler注解然后定義捕獲異常的類(lèi)型即可對(duì)這些捕獲的異常進(jìn)行統(tǒng)一的處理。
我們根據(jù)下面的這個(gè)示例來(lái)看該注解是如何使用吧。Spring Boot 基礎(chǔ)就不介紹了,看睛這個(gè):https://github.com/javastacks/spring-boot-best-practice
示例代碼:
- @ControllerAdvice
- public class MyExceptionHandler {
- @ExceptionHandler(value =Exception.class)
- public String exceptionHandler(Exception e){
- System.out.println("未知異常!原因是:"+e);
- return e.getMessage();
- }
- }
上述的示例中,我們對(duì)捕獲的異常進(jìn)行簡(jiǎn)單的二次處理,返回異常的信息,雖然這種能夠讓我們知道異常的原因,但是在很多的情況下來(lái)說(shuō),可能還是不夠人性化,不符合我們的要求。
那么我們這里可以通過(guò)自定義的異常類(lèi)以及枚舉類(lèi)來(lái)實(shí)現(xiàn)我們想要的那種數(shù)據(jù)吧。
自定義基礎(chǔ)接口類(lèi)
首先定義一個(gè)基礎(chǔ)的接口類(lèi),自定義的錯(cuò)誤描述枚舉類(lèi)需實(shí)現(xiàn)該接口。
代碼如下:
- public interface BaseErrorInfoInterface {
- /** 錯(cuò)誤碼*/
- String getResultCode();
- /** 錯(cuò)誤描述*/
- String getResultMsg();
- }
自定義枚舉類(lèi)
然后我們這里在自定義一個(gè)枚舉類(lèi),并實(shí)現(xiàn)該接口。
代碼如下:
- public enum CommonEnum implements BaseErrorInfoInterface {
- // 數(shù)據(jù)操作錯(cuò)誤定義
- SUCCESS("200", "成功!"),
- BODY_NOT_MATCH("400","請(qǐng)求的數(shù)據(jù)格式不符!"),
- SIGNATURE_NOT_MATCH("401","請(qǐng)求的數(shù)字簽名不匹配!"),
- NOT_FOUND("404", "未找到該資源!"),
- INTERNAL_SERVER_ERROR("500", "服務(wù)器內(nèi)部錯(cuò)誤!"),
- SERVER_BUSY("503","服務(wù)器正忙,請(qǐng)稍后再試!")
- ;
- /** 錯(cuò)誤碼 */
- private String resultCode;
- /** 錯(cuò)誤描述 */
- private String resultMsg;
- CommonEnum(String resultCode, String resultMsg) {
- this.resultCode = resultCode;
- this.resultMsg = resultMsg;
- }
- @Override
- public String getResultCode() {
- return resultCode;
- }
- @Override
- public String getResultMsg() {
- return resultMsg;
- }
- }
自定義異常類(lèi)
然后我們?cè)趤?lái)自定義一個(gè)異常類(lèi),用于處理我們發(fā)生的業(yè)務(wù)異常。
代碼如下:
- public class BizException extends RuntimeException {
- private static final long serialVersionUID = 1L;
- /**
- * 錯(cuò)誤碼
- */
- protected String errorCode;
- /**
- * 錯(cuò)誤信息
- */
- protected String errorMsg;
- public BizException() {
- super();
- }
- public BizException(BaseErrorInfoInterface errorInfoInterface) {
- super(errorInfoInterface.getResultCode());
- this.errorCode = errorInfoInterface.getResultCode();
- this.errorMsg = errorInfoInterface.getResultMsg();
- }
- public BizException(BaseErrorInfoInterface errorInfoInterface, Throwable cause) {
- super(errorInfoInterface.getResultCode(), cause);
- this.errorCode = errorInfoInterface.getResultCode();
- this.errorMsg = errorInfoInterface.getResultMsg();
- }
- public BizException(String errorMsg) {
- super(errorMsg);
- this.errorMsg = errorMsg;
- }
- public BizException(String errorCode, String errorMsg) {
- super(errorCode);
- this.errorCode = errorCode;
- this.errorMsg = errorMsg;
- }
- public BizException(String errorCode, String errorMsg, Throwable cause) {
- super(errorCode, cause);
- this.errorCode = errorCode;
- this.errorMsg = errorMsg;
- }
- public String getErrorCode() {
- return errorCode;
- }
- public void setErrorCode(String errorCode) {
- this.errorCode = errorCode;
- }
- public String getErrorMsg() {
- return errorMsg;
- }
- public void setErrorMsg(String errorMsg) {
- this.errorMsg = errorMsg;
- }
- public String getMessage() {
- return errorMsg;
- }
- @Override
- public Throwable fillInStackTrace() {
- return this;
- }
- }
自定義數(shù)據(jù)格式
順便這里我們定義一下數(shù)據(jù)的傳輸格式。
代碼如下:
- public class ResultBody {
- /**
- * 響應(yīng)代碼
- */
- private String code;
- /**
- * 響應(yīng)消息
- */
- private String message;
- /**
- * 響應(yīng)結(jié)果
- */
- private Object result;
- public ResultBody() {
- }
- public ResultBody(BaseErrorInfoInterface errorInfo) {
- this.code = errorInfo.getResultCode();
- this.message = errorInfo.getResultMsg();
- }
- public String getCode() {
- return code;
- }
- public void setCode(String code) {
- this.code = code;
- }
- public String getMessage() {
- return message;
- }
- public void setMessage(String message) {
- this.message = message;
- }
- public Object getResult() {
- return result;
- }
- public void setResult(Object result) {
- this.result = result;
- }
- /**
- * 成功
- *
- * @return
- */
- public static ResultBody success() {
- return success(null);
- }
- /**
- * 成功
- * @param data
- * @return
- */
- public static ResultBody success(Object data) {
- ResultBody rb = new ResultBody();
- rb.setCode(CommonEnum.SUCCESS.getResultCode());
- rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
- rb.setResult(data);
- return rb;
- }
- /**
- * 失敗
- */
- public static ResultBody error(BaseErrorInfoInterface errorInfo) {
- ResultBody rb = new ResultBody();
- rb.setCode(errorInfo.getResultCode());
- rb.setMessage(errorInfo.getResultMsg());
- rb.setResult(null);
- return rb;
- }
- /**
- * 失敗
- */
- public static ResultBody error(String code, String message) {
- ResultBody rb = new ResultBody();
- rb.setCode(code);
- rb.setMessage(message);
- rb.setResult(null);
- return rb;
- }
- /**
- * 失敗
- */
- public static ResultBody error( String message) {
- ResultBody rb = new ResultBody();
- rb.setCode("-1");
- rb.setMessage(message);
- rb.setResult(null);
- return rb;
- }
- @Override
- public String toString() {
- return JSONObject.toJSONString(this);
- }
- }
自定義全局異常處理類(lèi)
最后我們?cè)趤?lái)編寫(xiě)一個(gè)自定義全局異常處理的類(lèi)。
代碼如下:
- @ControllerAdvice
- public class GlobalExceptionHandler {
- private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
- /**
- * 處理自定義的業(yè)務(wù)異常
- * @param req
- * @param e
- * @return
- */
- @ExceptionHandler(value = BizException.class)
- @ResponseBody
- public ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){
- logger.error("發(fā)生業(yè)務(wù)異常!原因是:{}",e.getErrorMsg());
- return ResultBody.error(e.getErrorCode(),e.getErrorMsg());
- }
- /**
- * 處理空指針的異常
- * @param req
- * @param e
- * @return
- */
- @ExceptionHandler(value =NullPointerException.class)
- @ResponseBody
- public ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e){
- logger.error("發(fā)生空指針異常!原因是:",e);
- return ResultBody.error(CommonEnum.BODY_NOT_MATCH);
- }
- /**
- * 處理其他異常
- * @param req
- * @param e
- * @return
- */
- @ExceptionHandler(value =Exception.class)
- @ResponseBody
- public ResultBody exceptionHandler(HttpServletRequest req, Exception e){
- logger.error("未知異常!原因是:",e);
- return ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);
- }
- }
因?yàn)檫@里我們只是用于做全局異常處理的功能實(shí)現(xiàn)以及測(cè)試,所以這里我們只需在添加一個(gè)實(shí)體類(lèi)和一個(gè)控制層類(lèi)即可。
實(shí)體類(lèi)
又是萬(wàn)能的用戶(hù)表 (▽)
代碼如下:
- public class User implements Serializable{
- private static final long serialVersionUID = 1L;
- /** 編號(hào) */
- private int id;
- /** 姓名 */
- private String name;
- /** 年齡 */
- private int age;
- public User(){
- }
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public String toString() {
- return JSONObject.toJSONString(this);
- }
- }
Controller 控制層
控制層這邊也比較簡(jiǎn)單,使用Restful風(fēng)格實(shí)現(xiàn)的CRUD功能,不同的是這里我故意弄出了一些異常,好讓這些異常被捕獲到然后處理。這些異常中,有自定義的異常拋出,也有空指針的異常拋出,當(dāng)然也有不可預(yù)知的異常拋出(這里我用類(lèi)型轉(zhuǎn)換異常代替),那么我們?cè)谕瓿纱a編寫(xiě)之后,看看這些異常是否能夠被捕獲處理成功吧!
代碼如下:
- @RestController
- @RequestMapping(value = "/api")
- public class UserRestController {
- @PostMapping("/user")
- public boolean insert(@RequestBody User user) {
- System.out.println("開(kāi)始新增...");
- //如果姓名為空就手動(dòng)拋出一個(gè)自定義的異常!
- if(user.getName()==null){
- throw new BizException("-1","用戶(hù)姓名不能為空!");
- }
- return true;
- }
- @PutMapping("/user")
- public boolean update(@RequestBody User user) {
- System.out.println("開(kāi)始更新...");
- //這里故意造成一個(gè)空指針的異常,并且不進(jìn)行處理
- String str=null;
- str.equals("111");
- return true;
- }
- @DeleteMapping("/user")
- public boolean delete(@RequestBody User user) {
- System.out.println("開(kāi)始刪除...");
- //這里故意造成一個(gè)異常,并且不進(jìn)行處理
- Integer.parseInt("abc123");
- return true;
- }
- @GetMapping("/user")
- public List<User> findByUser(User user) {
- System.out.println("開(kāi)始查詢(xún)...");
- List<User> userList =new ArrayList<>();
- User user2=new User();
- user2.setId(1L);
- user2.setName("xuwujing");
- user2.setAge(18);
- userList.add(user2);
- return userList;
- }
- }
App 入口
和普通的SpringBoot項(xiàng)目基本一樣。
代碼如下:
- @SpringBootApplication
- public class App
- {
- public static void main( String[] args )
- {
- SpringApplication.run(App.class, args);
- System.out.println("程序正在運(yùn)行...");
- }
- }
功能測(cè)試
我們成功啟動(dòng)該程序之后,使用Postman工具來(lái)進(jìn)行接口測(cè)試。
首先進(jìn)行查詢(xún),查看程序正常運(yùn)行是否ok,使用GET 方式進(jìn)行請(qǐng)求。
GET http://localhost:8181/api/user
返回參數(shù)為:
{"id":1,"name":"xuwujing","age":18}
示例圖:
可以看到程序正常返回,并沒(méi)有因自定義的全局異常而影響。
然后我們?cè)賮?lái)測(cè)試下自定義的異常是否能夠被正確的捕獲并處理。
使用POST方式進(jìn)行請(qǐng)求
POST http://localhost:8181/api/user
Body參數(shù)為:
{"id":1,"age":18}
返回參數(shù)為:
{"code":"-1","message":"用戶(hù)姓名不能為空!","result":null}
示例圖:
可以看出將我們拋出的異常進(jìn)行數(shù)據(jù)封裝,然后將異常返回出來(lái)。
然后我們?cè)賮?lái)測(cè)試下空指針異常是否能夠被正確的捕獲并處理。在自定義全局異常中,我們除了定義空指針的異常處理,也定義最高級(jí)別之一的Exception異常,那么這里發(fā)生了空指針異常之后,它是回優(yōu)先使用哪一個(gè)呢?這里我們來(lái)測(cè)試下。
使用PUT方式進(jìn)行請(qǐng)求。
PUT http://localhost:8181/api/user
Body參數(shù)為:
{"id":1,"age":18}
返回參數(shù)為:
{"code":"400","message":"請(qǐng)求的數(shù)據(jù)格式不符!","result":null}
示例圖:
我們可以看到這里的的確是返回空指針的異常護(hù)理,可以得出全局異常處理優(yōu)先處理子類(lèi)的異常。
那么我們?cè)趤?lái)試試未指定其異常的處理,看該異常是否能夠被捕獲。
使用DELETE方式進(jìn)行請(qǐng)求。
DELETE http://localhost:8181/api/user
Body參數(shù)為:
{"id":1}
返回參數(shù)為:
{"code":"500","message":"服務(wù)器內(nèi)部錯(cuò)誤!","result":null}
這里可以看到它使用了我們?cè)谧远x全局異常處理類(lèi)中的Exception異常處理的方法。
到這里,測(cè)試就結(jié)束了。
順便再說(shuō)一下,自義定全局異常處理除了可以處理上述的數(shù)據(jù)格式之外,也可以處理頁(yè)面的跳轉(zhuǎn),只需在新增的異常方法的返回處理上填寫(xiě)該跳轉(zhuǎn)的路徑并不使用ResponseBody 注解即可。
細(xì)心的同學(xué)也許發(fā)現(xiàn)了在GlobalExceptionHandler類(lèi)中使用的是ControllerAdvice注解,而非RestControllerAdvice注解,如果是用的RestControllerAdvice注解,它會(huì)將數(shù)據(jù)自動(dòng)轉(zhuǎn)換成JSON格式,這種于Controller和RestController類(lèi)似,所以我們?cè)谑褂萌之惓L幚淼闹罂梢赃M(jìn)行靈活的選擇處理。