1. 程式人生 > >Spring 異常處理之全域性處理

Spring 異常處理之全域性處理

承接上文Spring異常處理之本地處理,本文介紹spring異常處理的第三種方式,也就是全域性處理。為什麼將該方式取名為全域性處理,其實很簡單,因為該方式本質上和本地處理是一樣的,無非就是本地處理將異常處理方法或者說異常處理邏輯直接寫在controller中,而全域性處理其實就是把本地處理中的異常處理方法抽取出來,放到一個地方集中管理。

  • 控制器以及業務方法
/**
 * 結合 GlobalExceptionHandlerControllerAdvice 使用
 */
@Controller
@RequestMapping(value = {"/global"})
public class WithoutExceptionHandlerController {

    /**
     * Throws an unannotated <tt>DataIntegrityViolationException</tt>. Must be caught by an
     * exception handler.
     *
     * @return Nothing - it always throws the exception.
     * @throws DataIntegrityViolationException Always thrown.
     */
    @RequestMapping("/dataIntegrityViolation")
    String throwDataIntegrityViolationException() {
        throw new DataIntegrityViolationException("ID 重複");
    }

    /**
     * Simulates a database exception by always throwing <tt>SQLException</tt>. Must be caught by an
     * exception handler.
     *
     * @return Nothing - it always throws the exception.
     * @throws SQLException Always thrown.
     */
    @RequestMapping("/sqlException")
    public String throwSqlException() throws SQLException {
        throw new SQLException();
    }

    /**
     * Always throws a <tt>SupportInfoException</tt>. Must be caught by an exception handler.
     *
     * @return Nothing - it always throws the exception.
     */
    @RequestMapping("/supportInfoException")
    public String throwCustomException() {
        throw new SupportInfoException("出錯了");
    }

}
  • 集中在一起的異常處理邏輯
/**
 * 結合 WithoutExceptionHandlerController 使用
 * 
 * 對比 ExceptionHandlerController 可以發現,其實這個全域性異常處理就是通過使用 ControllerAdvice 註解將 ExceptionHandler
 * 
 * 從 controller 中提取出來集中到一處
 */
@ControllerAdvice
public class GlobalExceptionHandlerControllerAdvice {

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

    /**
     * 異常處理之 @ControllerAdvice 之一
     */
    @ResponseStatus(value = HttpStatus.CONFLICT, reason = "資料衝突")
    @ExceptionHandler(DataIntegrityViolationException.class)
    public void conflict() {
        // 什麼也不做
    }

    /**
     * 異常處理之使用 @ControllerAdvice 之二 | 指定了 view
     *
     * @param exception 異常物件
     * @return 檢視名稱
     */
    @ExceptionHandler({SQLException.class})
    public String databaseError(Exception exception) {
        logger.info(exception.toString());
        return "database.error";
    }

    /**
     * 異常處理之使用 @ControllerAdvice 之三 | 完全控制 - model 、view 、有用的異常資訊
     *
     * @param req 當前的 HTTP 請求物件.
     * @param exception 丟擲的異常 - 也就是 {@link SupportInfoException}.
     * @return 模型和檢視
     * @throws Exception 異常
     */
    @ExceptionHandler(SupportInfoException.class)
    public ModelAndView handleError(HttpServletRequest req, Exception exception) throws Exception {
        if (AnnotationUtils.findAnnotation(exception.getClass(), ResponseStatus.class) != null)
            throw exception;

        logger.error("Request: " + req.getRequestURI() + " raised " + exception);

        ModelAndView mav = new ModelAndView();
        // 注意這裡,這個 exception 是一個物件 | 而 DefaultErrorAttributes 中的 exception 是異常物件的完全限定名
        mav.addObject("exception", exception);
        mav.addObject("url", req.getRequestURL());
        mav.addObject("timestamp", new Date().toString());
        mav.addObject("status", 500);

        mav.setViewName("support.error");
        return mav;
    }

}

對比上一篇文章我們可以發現,唯一的不同之處就是該方式通過使用@ControllerAdvice註解將所有的異常處理邏輯集中起來了。

到這裡為止,三種異常處理的方式就介紹完了。其實我們可以看出來,本地處理全域性處理其實是一樣的。關於到底使用哪種方式更合適呢,個人認為第三種是最合適的,因為第一種需要繫結HTTP狀態碼,第二種將異常處理邏輯直接寫在controller中,導致業務程式碼和異常處理程式碼混在一起,第三種明顯靈活的多。

  • 參考