@ControllerAdvice全局異常處理

Exception,分爲運行時異常(RuntimeException)和非運行時異常
可查的異常(checked exceptions): Exception下除了RuntimeException外的異常
不可查的異常(unchecked exceptions):RuntimeException及其子類和錯誤(Error)

在這裏插入圖片描述
可查的異常在我們編碼的時候就會catch解決,運行時異常則是不可控的,比如一些空指針異常,數組越界之類的異常。
代碼裏到處寫try-catch也不太好,這時候就需要利用AOP做全局異常處理。


一、設計方案

  • 有多語言支持,需要一個語言本地化工具類(沒這個需求的可不要) ,InternationalizationUtil.java
  • 定義一個業務異常類,必須繼承自RuntimeException, BusinessException.java
  • 定義一個業務異常code類,BusinessErrorCode.java
  • 定義Controller增強,ExceptionAdvice.java
  • 統一返回格式,Result.java
    在業務邏輯處理中會有業務邏輯出錯的提示,比如提示用戶密碼錯誤,餘額不足等等。這些信息都需要傳給前端,提示給用戶。
    流程:業務邏輯出錯,拋個BusinessException異常,傳入異常BusinessErrorCode,ExceptionAdvice捕獲異常進行處理,根據Code,調用本地化語言工具類獲取到對應語言的提示信息,封裝爲Result返回給前端。

二、代碼

自定義BusinessException業務異常類。注意,必須繼承RuntimeException

public class BusinessException extends RuntimeException {

    private static final long serialVersionUID = 5317403756736254689L;

    private int code;

    private Object[] args;
    public BusinessException(int messageCode) {
        super(getCodeMessage(messageCode));
        this.code = messageCode;
    }
    public BusinessException(int messageCode,Object... args) {
        super(getCodeMessage(messageCode));
        this.code = messageCode;
        this.args = args;
    }
    private static String getCodeMessage(int messageCode) {
        List<String> fieldName = new ArrayList<>();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        try {
            Class businessErrorCode = classLoader.loadClass("com.demo.common.BusinessErrorCode");
            Field[] fields = businessErrorCode.getDeclaredFields();
            List<Field> fieldList = Arrays.asList(fields);
            fieldList.stream().forEach(field -> {
                try {
                    field.isAccessible();
                    if (Integer.parseInt(field.get(businessErrorCode).toString()) == messageCode) {
                        fieldName.add(field.getName());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            return fieldName.get(0);
        } catch (Exception e) {
            e.printStackTrace();
            return "FAIL";
        }
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Object[] getArgs() {
        return args;
    }

    public void setArgs(Object[] args) {
        this.args = args;
    }
}

我這裏因爲做國際化,考慮到日誌信息顯示,對code和message做了特殊處理。一般需求可以直接不要這個getCodeMessage()方法。
定義BusinessErrorCode

public class BusinessErrorCode {
	/**
     * 參數錯誤!
     */
    public static final int PARAMETER_FAIL = 10000;
}

假如service裏有這麼一段,拋出參數錯誤的異常

    @Override
    public void changeDefaultGradeNo(Long defaultGradeNo, Long groupId, Long uid) {
        logger.info("groupId:{} defaultGradeNo:{}", groupId, defaultGradeNo);
        if (defaultGradeNo == null) {
            throw new BusinessException(BusinessErrorCode.PARAMETER_FAIL);
        }
    }

在Controller不需要對這個service的changeDefaultGradeNo方法做try-catch處理,用AOP知識,寫一個異常增強類統一攔截異常,封裝Result返回給前端。
定義異常增強類ExceptionAdvice

@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {

    private Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);

    @Autowired
    private InternationalizationUtil i18nUtil;

    /**
     * 處理BusinessException異常返回信息
     *
     * @param businessException
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public Result handleBusinessException(BusinessException businessException) {
        String message = businessException.getMessage();
        Integer errorCode = businessException.getCode();
        if (StringUtils.isEmpty(errorCode.toString())) {
            errorCode = SystemErrorCode.SYSTEM_ERROR;
        }
        String resultMessage = i18nUtil.i18n(errorCode+"",businessException.getArgs());
        logger.info("業務異常:{}-{}-{}", errorCode, message, resultMessage);
        return new Result(errorCode, resultMessage);
    }

    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public Object handle(RuntimeException runtimeException) {
        logger.error("運行時異常:", runtimeException);
        return new Result(BusinessErrorCode.FAIL, i18nUtil.i18n(SystemErrorCode.SYSTEM_ERROR));
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object handle(Exception exception) {
        logger.error("異常:", exception);
        return new Result(BusinessErrorCode.FAIL, i18nUtil.i18n(SystemErrorCode.SYSTEM_ERROR));
    }
}

可以定義好對不同異常的不同處理方式。
關於本地化語言工具類InternationalizationUtil

@Component
public class InternationalizationUtil {

    @Autowired
    private MessageSource messageSource;

    /**
     * 根據errorCode和本地化對象Local獲取國際化提示信息
     *
     * @param errorCode
     * @return
     */
    public String i18n(int errorCode) {
        return i18n(String.valueOf(errorCode));
    }

    public String i18n(String errorCode) {
        return messageSource.getMessage(errorCode, null, errorCode, LocaleContextHolder.getLocale());
    }

    public String i18n(String errorCode, Object[] args) {
        return messageSource.getMessage(errorCode, args, LocaleContextHolder.getLocale());
    }
}

如果不用spring默認的文件配置,要指定message資源的位置,參考Spring國際化

spring:
  profiles:
    active: dev
  messages:
    basename: i18n/messages
    encoding: UTF-8

在這裏插入圖片描述
對應的中文,英文資源文件
在這裏插入圖片描述
最後看結果封裝類Result

public class Result {

    public static final String SUCCESS_MESSAGE = "";
    private int code = BusinessErrorCode.OK;
    private String message = SUCCESS_MESSAGE;
    private Object data;
}

這樣一套流程走下來,前端看到的就是

{
	"code": 10000,
	"message":"參數錯誤",
	"data":
}

如果不需要國際化,會簡單些。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章