Spring Boot實戰系列(4)統一異常處理

五月君發表於2019-03-04

專案開發中保證零異常似乎是不可能的,不論是系統異常還是程式本身的編碼問題造成的異常資訊都要以一種約定的資料結構返回,友好的處理方式在前後端分離模式下(後端提供API介面給到前端)能大大增加大家的溝通、工作效率。基於Spring Boot進行異常統一處理,本文中主要用到@ControllerAdvice註解。

快速導航

  • 統一返回資料結構
    • [統一返回資料結構] 定義介面返回資料結構
    • [統一返回資料結構] 資料介面欄位模型定義
    • [統一返回資料結構] 封裝介面返回方法(成功、失敗)
  • 統一異常處理
    • [統一異常處理] 狀態訊息列舉
    • [統一異常處理] 自定義異常類
    • [統一異常處理] @ControllerAdvice統一處理異常
  • 測試
    • [測試] 測試正常返回及空指標系統異常
    • [測試] 自定義異常測試

本文示例基於Spring Boot實戰系列(3)AOP面向切面程式設計 /chapter3/chapter3-1可在Github獲取原始碼

統一返回資料結構

定義介面返回資料結構

先定義介面返回資料結構,code為0表示操作成功,非0表示異常。其中data只有在處理成功才會返回,其他情況不會返回,或者那些不需要返回資料的介面(更新、刪除…)

{
 	"code": 0,
 	"message": "SUCCESS",
 	"data": {

 	}
}
複製程式碼

資料介面欄位模型定義

建立/domain/Result.java類,對以上資料介面涉及的欄位進行定義。

Result.java

package com.angelo.domain;

public class Result<T> {
    private Integer code; // 狀態碼

    private String message; // 狀態描述資訊

    private T data; // 定義為範型

    // 以下 getter、setter方法省略
}
複製程式碼

封裝介面返回方法

建立/util/MessageUtil.java類,對返回的成功、失敗進行統一封裝。

MessageUtil.java

package com.angelo.util;

import com.angelo.domain.Result;

public class MessageUtil {

    /**
     * 成功方法
     * @param object
     * @return
     */
    public static Result success(Object object) {
        Result result = new Result();
        result.setCode(0);
        result.setMessage("SUCCESS");
        if (object != null) {
            result.setData(object);
        }

        return result;
    }

    /**
     * 成功但是
     */
    public static Result success() {
        return success(null);
    }

    /**
     * 失敗方法
     * @param code
     * @param message
     * @return
     */
    public static Result error(Integer code, String message) {
        Result result = new Result();
        result.setCode(code);
        result.setMessage(message);

        return result;
    }
}
複製程式碼

統一異常處理

狀態訊息列舉

專案用到的狀態碼、描述資訊要有個檔案統一去做列舉定義,一方面可以實現複用,另一方面如果狀態碼、描述有改動只需要在定義列舉的地方改動即可。

新建/enums/MessageEnum.java列舉

package com.angelo.enums;

public enum MessageEnum {
    SYSTEM_ERROR(1001, "系統異常"),
    NAME_EXCEEDED_CHARRACTER_LIMIT(2000, "姓名超過了限制,最大4個字元!");

    private Integer code;

    private String message;

    MessageEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}
複製程式碼

自定義異常類

Spring Boot框架只對丟擲的RuntimeException異常進行事物回滾,那麼Spring Boot封裝的RuntimeException異常也是繼承的Exception

新建/exception/UserException.java類,繼承於RuntimeException

UserException.java

package com.angelo.exception;

import com.angelo.enums.MessageEnum;

public class UserException extends RuntimeException {
    private Integer code;

    public UserException(MessageEnum messageEnum) {
        super(messageEnum.getMessage());
        this.code = messageEnum.getCode();
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}
複製程式碼

@ControllerAdvice統一處理異常

關於@ControllerAdvice更多內容可參考官方文件https://docs.spring.io/spring-framework/docs/5.0.0.M1/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html

  • @ControllerAdvice,spring3.2新增加,用於定義 @ExceptionHandler, @InitBinder, 和 @ModelAttribute方法,並應用到所有的@RequestMapping方法。

  • @ExceptionHandler,攔截異常,方法裡的value是指需要攔截的異常型別,通過該註解可實現自定義異常處理。

  • 注意: 之前講過AOP面向切面程式設計,註解@AfterThrowing會捕捉到專案中的錯誤資訊,如果使用了此註解,它捕獲到錯誤資訊之後,會直接返回,是不會觸發@ControllerAdvice註解的。
package com.angelo.handle;

import com.angelo.domain.Result;
import com.angelo.exception.UserException;
import com.angelo.util.MessageUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class ExceptionHandle {

    private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Result handle(Exception e) {
        logger.info("進入error");

        // 是否屬於自定義異常
        if (e instanceof UserException) {
            UserException userException = (UserException) e;

            return MessageUtil.error(userException.getCode(), userException.getMessage());
        } else {
            logger.error("系統異常 {}", e);

            return MessageUtil.error(1000, "系統異常!");
        }
    }
}
複製程式碼

測試

測試正常返回及空指標系統異常

修改Usercontroller.java類,在查詢使用者列表介面增加返回值處理,如下所示:

/**
 * 查詢使用者列表
 * @return
 */
@RequestMapping(value = "/user/list/{exception}")
public Result<User> userList(@PathVariable("exception") Boolean exception) {
    if (exception) {
        return null; // 測試空指標異常
    }

    return MessageUtil.success(userRepository.findAll());
}
複製程式碼
  • 返回成功
圖片描述
  • 返回系統異常
圖片描述

自定義異常測試

修改Usercontroller.java類,在儲存使用者資訊介面增加姓名長度校驗丟擲自定義錯誤,如下所示:

/**
 * 儲存一個使用者
 * @param name
 * @param age
 * @return
 */
@PostMapping(value = "/user")
public User userAdd(@RequestBody User userParams) throws Exception {
    User user = new User();
    user.setName(userParams.getName());
    user.setAge(userParams.getAge());

    if (userParams.getName().length() > 4) { // 校驗測試異常類
        throw new UserException(MessageEnum.NAME_EXCEEDED_CHARRACTER_LIMIT);
    }

    return userRepository.save(user);
}
複製程式碼
圖片描述

Github檢視本文完整示例 chapter4-1

作者:五月君
連結:www.imooc.com/article/260…
來源:慕課網
Github: Spring Boot實戰系列

相關文章