Spring boot/Spring 統一錯誤處理方案的使用

擁抱心中的夢想發表於2018-06-14

當我們開發spring web應用程式時,對於如IOException,ClassNotFoundException之類的檢查異常,往往編譯器會提示程式設計師採用try-catch進行顯式捕獲,而對於像ClassCastException,NullPointerException這類非檢查異常,編譯器是不會提示你了,這往往也是能體現程式設計師程式碼編寫能力的一個方面。

在spring web特別是spring-boot應用中,當一個請求呼叫成功時,一般情況下會返回json格式的物件,就像下面圖所示:

Spring boot/Spring 統一錯誤處理方案的使用

但如果請求丟擲了一個RuntimeException呢?如果我們不做處理,再次呼叫時將出現下面的頁面:

Spring boot/Spring 統一錯誤處理方案的使用

也就是說當呼叫出現錯誤時,spring-boot預設會將請求對映到/error路徑中去,如果沒有相應的路徑請求處理器,那麼就會返回上面的Whitelabel錯誤頁面。

1、自定義錯誤處理頁面

當然對執行時異常不做處理是不可能的啦!通常的做法是自定義統一錯誤頁面,然後返回。按照上面的思路,我們實現一個請求路徑為/error的控制器,控制器返回一個資源路徑地址,定義請求對映路徑為/error的控制器並實現ErrorController介面,程式碼如下:

MyErrorPageController

package com.example.demo.controller.handler.errorpage;

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 
 * The class MyErrorPageController.
 *
 * Description:自定義錯誤頁面
 *
 * @author: huangjiawei
 * @since: 2018年6月13日
 * @version: $Revision$ $Date$ $LastChangedBy$
 *
 */
@Controller
public class MyErrorPageController implements ErrorController {
    
    @RequestMapping("/error")
    public String handleError() {
    	return "error.html"; // 該資源位於resources/static目錄下
    }
    
    @Override
    public String getErrorPath() {
    	return null;
    }
}
複製程式碼

然後在reosurces/static目錄下建立error.html檔案:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>這是個錯誤頁面!存放在resources/static目錄下,spring-boot發生錯誤時預設呼叫</h1>
</body>
</html>
複製程式碼

再次請求http://localhost:7000/demo/getUserInfoWithNoHandler.json,如下:

Spring boot/Spring 統一錯誤處理方案的使用

2、使用@ControllerAdvice@ResponseBody@ExceptionHandler統一處理異常

在spring中可以使用上面3個註解進行統一異常處理,預設情況下我們可以針對系統中出現的某種型別的異常定義一個統一的處理器handler,比如說系統丟擲了一個NullPointerException,那麼我們可以定義一個專門針對NullPointerException的處理器,程式碼如下:

getUserInfoWithNullPointerException介面

/**
 * 測試空指標錯誤的處理
 * @return
 * @throws NullPointerException
 */
@RequestMapping(value = "getUserInfoWithNullPointerException.json", method = RequestMethod.GET)
public Student getUserInfoWithNullPointerException() throws NullPointerException {
	throw new NullPointerException();
}
複製程式碼

NullPointerExceptionHandler.java

package com.example.demo.controller.handler;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.demo.pojo.ErrorReturn;

/**
 * 
 * The class NullPointerExceptionHandler.
 *
 * Description:處理空指標
 *
 * @author: huangjiawei
 * @since: 2018年6月13日
 * @version: $Revision$ $Date$ $LastChangedBy$
 *
 */
@ControllerAdvice
public class NullPointerExceptionHandler {
    @ExceptionHandler(NullPointerException.class)
    @ResponseBody
    public ErrorReturn dealNullPointerException() {
        e.printStackTrace();
    	ErrorReturn error = new ErrorReturn();
    	error.setReturnCode("-1");
    	error.setDesc("出現空指標異常啦!");
    	return error;
    }
}
複製程式碼

瀏覽器執行:http://localhost:7000/demo/getUserInfoWithNullPointerException.json

Spring boot/Spring 統一錯誤處理方案的使用

同樣的道理,如果我們還需要為其他的執行時異常提供統一的處理器,那麼也可以像上面一樣為每一個異常型別定義一個處理器,比如我們又想為ArithmeticException定義處理器,那麼我們只需要建立一個類或者方法,然後在方法上的@ExceptionHanler註解內加上ArithmeticException.class指定異常型別即可。

不過你有沒有發現,這樣為每種異常型別定義一個異常處理類或者方法,因為執行時異常型別特別多,不可能為每種型別都指定一個處理器類或方法,針對這種情況,spring也是可以解決的。如果我們沒有為某種特定型別異常,如ArithmeticException定義處理器,那麼我們可以定義一個Exception或者Throwable處理器統一處理。

這樣做的好處是,減少了處理器類的數量,同時將異常處理轉移到父類上面去,這也是繼承的一大優勢吧!但是,當你既定義了特定型別的異常,同時又定義了Exception異常的處理器,那麼要小心了,這裡不一定有優先順序的關係,也就是說不一定會出現只執行父異常處理器的情況,可能是隻執行A處理器,而不執行B處理器或者只執行B處理器,不執行A處理器。如NullPointerExceptionHandler異常會向Exception異常傳遞(但ArithmeticException不會向Exception傳遞)

現在假設我們既定義上面的NullPointerExceptionHandler,又定義了下面的ExceptionThrowableHandler,那麼當發生NullPointerException時,就會預設執行ExceptionThrowableHandler的方法。

ExceptionThrowableHandler.java

package com.example.demo.controller.handler;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.demo.pojo.ErrorReturn;

/**
 * 
 * The class ExceptionThrowableHandler.
 *
 * Description:有些異常會向高階別異常傳遞(但ArithmeticException不會向Exception傳送)
 *
 * @author: huangjiawei
 * @since: 2018年6月13日
 * @version: $Revision$ $Date$ $LastChangedBy$
 *
 */
@ControllerAdvice
public class ExceptionThrowableHandler {
    
    @ExceptionHandler(Throwable.class)
    @ResponseBody
    public ErrorReturn dealThrowable() {
    	ErrorReturn error = new ErrorReturn();
    	error.setDesc("處理Throwable!");
    	error.setReturnCode("-1");
    	return error;
    }
    
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ErrorReturn dealCommonException() {
    	ErrorReturn error = new ErrorReturn();
    	error.setReturnCode("-1");
    	error.setDesc("公共異常處理!");
    	return error;
    }
}
複製程式碼

瀏覽器執行 : http://localhost:7000/demo/getUserInfoWithNullPointerException.json

Spring boot/Spring 統一錯誤處理方案的使用

可以發現只執行Exception的處理器,沒有執行空指標的處理器,也就是異常處理往上傳送了。下面再來看看丟擲ArithmeticException的情況:

getUserInfoWithArithmeticException.json

/**
 * 測試ArithmeticException錯誤的處理
 * @return
 * @throws ArithmeticException
 */
@RequestMapping(value = "getUserInfoWithArithmeticException.json", method = RequestMethod.GET)
public Student getUserInfoWithArithmeticException() throws ArithmeticException {
	throw new ArithmeticException();
}
複製程式碼

ArithmeticExceptionHandler.java

package com.example.demo.controller.handler;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.demo.pojo.ErrorReturn;

@ControllerAdvice
public class ArithmeticExceptionHandler {
    /**
     * 處理ArithmeticException異常
     * @return
     */
    @ResponseBody
    @ExceptionHandler(ArithmeticException.class)
    public ErrorReturn dealArithmeticException() {
    	ErrorReturn errorObject = new ErrorReturn();
    	errorObject.setReturnCode("-1");
    	errorObject.setDesc("算數處理出現異常!");
    	return errorObject;
    }
}
複製程式碼

瀏覽器執行 : http://localhost:7000/demo/getUserInfoWithArithmeticException.json

Spring boot/Spring 統一錯誤處理方案的使用

結果發現異常處理並沒有往上層的ExceptionHandler傳送。

總結:對於既定義特定型別的處理器,又定義Exception等父型別的處理器時要特別小心,並不是所有的異常都會往上級處理,如果我們想只減少處理器類的數量,不想為每種特定型別的處理器新增類或者方法,那麼小編建議使用instanceof關鍵字對異常型別進行判斷即可。

如下面的程式碼,我們只建立一個公共的異常處理器,處理Exception異常,同時使用instanceof進行判斷。

@ExceptionHandler(Exception.class)
@ResponseBody
public ErrorReturn dealCommonException(Exception e) {
    ErrorReturn error = new ErrorReturn();
    // 此處可以採用 instanceof 判斷異常型別
    if (e instanceof ArithmeticException) {
    	error.setReturnCode("-1");
    	error.setDesc("算數異常處理!");
    	return error;
    }
    System.err.println("exception");
    error.setReturnCode("-1");
    error.setDesc("公共異常處理!");
    return error;
}
複製程式碼

瀏覽器執行丟擲ArithmeticException的介面,如下:

Spring boot/Spring 統一錯誤處理方案的使用

本文程式碼地址: github.com/SmallerCode…

謝謝閱讀,歡迎start,指正!

相關文章