SpringBoot 統(tǒng)一異常處理

SpringBoot 統(tǒng)一異常處理

SpringBoot異常處理

異常和響應碼

因為用RESTful設計的接口, 應該用狀態(tài)碼反映請求的錯誤, 不應該統(tǒng)一返回200 的狀態(tài)碼, 然后再通過 msg 來描述錯誤. 所以統(tǒng)一異常處理比較關鍵.

異常一般分為 業(yè)務異常非業(yè)務異常

狀態(tài)碼 使用場景
400 bad request 常用在參數(shù)校驗
401 unauthorized 未經(jīng)驗證的用戶,常見于未登錄。如果經(jīng)過驗證后依然沒權限,應該 403(即 authentication 和 authorization 的區(qū)別)。
403 forbidden 無權限
404 not found 資源不存在
500 internal server error 非業(yè)務類異常
503 service unavaliable 由容器拋出,自己的代碼不要拋這個異常

業(yè)務異常通常返回4xx的狀態(tài)碼

非業(yè)務異常只需要返回 500 , 提示 服務器錯誤,請稍候重試

默認異常處理

SpringBoot 提供了默認的處理異常方式,當出現(xiàn)異常時就會默認映射到 /error。處理異常的程序在類BasicErrorController 中.

該類提供了兩種異常處理的方法 :

  • 方法 errorHtml 用于處理瀏覽器端請求時出現(xiàn)的異常.
  • 方法 error 用于處理機器客戶端請求時出現(xiàn)的異常。

這兩種請求的的區(qū)別在于請求頭中 Accept 的值 :

  • 值為 text/html 時,方法 errorHtml 執(zhí)行,返回 HTML 頁面。
  • 值為 application/json 時,方法 error 執(zhí)行,返回 json 數(shù)據(jù)。

errorHtmlerror 兩個方法的源代碼

@RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    }

    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(body, status);
    }

瀏覽器端錯誤頁面

SpringBoot默認404錯誤頁面

如果想自定義頁面替換這個頁面, 你只需在 /error 文件夾下添加一個表示錯誤頁面的文件。該文件可以是一個靜態(tài)的HTML,也可以使用模板。這個文件的名字應該是精確的狀態(tài)碼或者是表示一個系列的模糊名稱。如下:

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>
src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.ftl
             +- <other templates>

其背后的原理在于上面提到的 errorHtml 方法。當出現(xiàn)異常時,該方法會查詢是否有在 error 文件夾下提供對應錯誤狀態(tài)碼的靜態(tài)資源文件,如果有則返回該文件,沒有則返回上小節(jié)講到的白色錯誤標簽頁。如果想要知道更詳細的細節(jié)請查看相關源碼。

JSON格式錯誤

當請求頭中的Accept的值為 application/json 時,就會返回 json 數(shù)據(jù)了。出現(xiàn)異常時,BasicErrorController 類中的 error 方法將被執(zhí)行。會獲取錯誤信息然后以 Json 格式返回。如下圖:

SpringBoot默認返回JSON的500錯誤

自定義異常處理

下面有兩種方式自定義異常處理

  • 對于應用級別的業(yè)務異常處理,我們可以通過注解 @ControllerAdvice@RestControllerAdvice 來實現(xiàn)異常處理。但是上面的注解只能捕獲處理應用級別的異常,例如 Controller 中拋出自定義的異常。卻不能處理容器級別的異常,例如 Filter 中拋出的異常等。
  • 要想處理容器級別的異常,需要繼承 BasicErrorController 類,重寫 errorHtmlerror 方法。或者實現(xiàn) ErrorController 接口,起到和類 BasicErrorController 相似的作用。

處理應用級別異常

下面是返回 JSON 格式的處理類

@RestControllerAdvice
public class ExpectedException {

    private static final long serialVersionUID = 1L;

    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        return new R<>(null, "缺少參數(shù)");
    }
}

  • @RestControllerAdvice@ControllerAdvice +@ResponseBody 起到的作用是一樣的
  • 注解 @ExceptionHandler 里面的 value 的類是我們捕獲的處理異常, 此類異常會被defaultErrorHandler 方法處理.
  • @ResponseStatus(HttpStatus.BAD_REQUEST) 注解, 指定了此異常返回的狀態(tài)碼, 因為是缺少參數(shù), 所以返回 400 的狀態(tài)碼
  • R 類為一個 ResultJSON 類, 把內容封裝起來, 一般有 code, meg , data 三個屬性. 返回一個 JSON 字符串.

處理容器級別的異常

@ControllerAdvice 注解的異常處理類并不能處理容器級別的異常, 我們可以通過繼承 BasicErrorController 類重寫 errorHtmlerror 方法,以達到我們想要的返回結果。

還可以通過實現(xiàn) ErrorController 接口的方法來實現(xiàn)自定義異常處理,BasicErrorController 類也是實現(xiàn)了 ErrorController 接口,因此這種方式和 BasicErrorController 類有相似作用。實現(xiàn)起來相對麻煩,本文只講解繼承 BasicErrorController 類的方式。

自定義錯誤處理類 CustomizeErrorController

@Controller
public class CustomizeErrorController extends BasicErrorController{

    public CustomizeErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers){
        super(errorAttributes, errorProperties, errorViewResolvers);
    }

    @RequestMapping(produces = {"text/html"})
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView == null ? new ModelAndView("error", model) : modelAndView;
    }

    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = this.getStatus(request);
        ApiResponseBody responseBody = new ApiResponseBody((int)body.get("status"), (String) body.get("error") + ": " + (String) body.get("message"));
        return new ResponseEntity(responseBody, status);
    }
}

該類將會覆蓋 BasicErrorController 類起到處理異常的作用。但這里要注意,如果想要保留對SpringBoot默認的對瀏覽器請求的異常處理(也就是根據(jù)異常錯誤狀態(tài)碼返回 error 文件夾下對應的錯誤頁面),還需新建一個配置文件 CustomErrorConfiguration

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@AutoConfigureBefore({WebMvcAutoConfiguration.class})
@EnableConfigurationProperties({ResourceProperties.class})
public class CustomErrorConfiguration {
    private final ServerProperties serverProperties;
    private final List<ErrorViewResolver> errorViewResolvers;

    public CustomErrorConfiguration(ServerProperties serverProperties, ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
        this.serverProperties = serverProperties;
        this.errorViewResolvers = (List)errorViewResolversProvider.getIfAvailable();
    }

    @Bean
    public CustomizeErrorController customizeErrorController(ErrorAttributes errorAttributes){
        return new CustomizeErrorController(errorAttributes, this.serverProperties.getError(),errorViewResolvers);
    }

}

參考: Spring boot 異常處理詳解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容