SpringBoot中異常處理

huan1993發表於2022-04-28

一、背景

在我們編寫程式的過程中,程式中可能隨時發生各種異常,那麼我們如何優雅的處理各種異常呢?

二、需求

1、攔截系統中部分異常,返回自定義的響應。

比如:
系統發生HttpRequestMethodNotSupportedException異常,我們需要返回如下資訊。
http的狀態碼:返回 405

{
    code: 自定義異常碼,
    message: 錯誤訊息
}

2、實現自定義異常的攔截

攔截我們自己寫的 BizException

三、編寫一些異常基礎程式碼

1、引入jar包

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>

注意:
引入spring-boot-starter-validation是為了驗證請求的中的引數,然後當引數不滿足時丟擲異常。

2、定義一個自定義異常

public class BizException extends RuntimeException {
    public BizException() {
    }
    public BizException(String message) {
        super(message);
    }
    public BizException(String message, Throwable cause) {
        super(message, cause);
    }
    public BizException(Throwable cause) {
        super(cause);
    }
    public BizException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

3、編寫一個簡單的控制層

@RestController
@RequestMapping("exception")
public class ExceptionTestController {

    static class Req {
        @NotBlank
        public String password;
    }

    @PostMapping("password")
    public String checkPassword(@Validated @RequestBody Req req) {

        if (Objects.equals(req.password, "exception")) {
            throw new BizException("密碼傳遞的是exception字串");
        }

        return "當前密碼,password: " + req.password;
    }
}

解釋
提供一個 /exception/password api,需要傳遞一個password引數
1、當不傳遞 password 引數時將丟擲MethodArgumentNotValidException異常。
2、當password傳遞exception引數時,則丟擲BizException異常。

4、測試

1、不傳遞password引數響應是什麼

1、使用預設的DefaultHandlerExceptionResolver處理

這個類DefaultHandlerExceptionResolver是預設自動配置的。
不傳遞password引數響應是什麼
從上圖中可以看出有一個預設欄位的返回值
DefaultHandlerExceptionResolver處理

2、使用ResponseEntityExceptionHandler處理
1、編寫異常處理程式碼-使用預設的邏輯
@RestControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        // 此處自定義返回值
        return super.handleMethodArgumentNotValid(ex, headers, status, request);
    }
}

可以看到handleMethodArgumentNotValid方法直接呼叫父類的方法,即使用預設的處理方式。
響應

從上圖中可以看出返回值是空

2、編寫異常處理程式碼-返回值返回自定義內容
@Component
@RestControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        // 此處自定義返回值
        return super.handleMethodArgumentNotValid(ex, headers, status, request);
    }
        
    @Override
    protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        Set<HttpMethod> supportedMethods = ex.getSupportedHttpMethods();

        // 自定義請求返回值
        Map<String, Object> body = new HashMap<>(4);
        body.put("code", "錯誤碼");
        body.put("message", "當前請求的方法不支援,支援的請求方法為:" + supportedMethods);

        return new ResponseEntity<>(body, headers, status);
    }
}

由上面的程式碼可知handleHttpRequestMethodNotSupported方法返回了自定義的body。
響應結果
從上圖中可以看出,返回了我們自己定義的返回值。

2、password引數傳遞exception

1、使用ResponseEntityExceptionHandler或DefaultHandlerExceptionResolver處理

password引數傳遞exception
由上圖可知返回結果不對,我們需要自定義返回結果。

2、返回自定義異常
1、編寫BizException處理程式碼
@RestControllerAdvice
public class BizExceptionHandler {

    @ExceptionHandler(BizException.class)
    public ResponseEntity<Object> handleBizException(BizException exception) {
        // 自定義請求返回值
        Map<String, Object> body = new HashMap<>(4);
        body.put("code", "錯誤碼");
        body.put("message", "異常資訊為:" + exception.getMessage());
        return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
2、測試返回結果

測試返回結果
從上圖可知返回了自定義資訊

四、注意事項

1、如果實現自定義異常處理

  1. 類上使用@RestControllerAdvice註解
  2. 方法上使用@ExceptionHandler來處理特定的異常

2、ResponseEntityExceptionHandler預設處理那些異常

ResponseEntityExceptionHandler預設處理那些異常

3、使用了ResponseEntityExceptionHandler後,為什麼發生了異常後返回體為空

異常返回值為null
預設情況下,實現了 ResponseEntityExceptionHandler這個類後,這個類處理的所有異常的響應結果都是 null,如果想返回別的值需要我們自己去處理。

五、總結

1、如果我們想處理自定義異常,則可以使用 @RestControllerAdvice || @ControllerAdvice 配置@ExceptionHandler來使用。
2、如果我們實現了ResponseEntityExceptionHandler來處理異常,那麼預設的異常的響應結果為空,如果想不為空,則需要我們自己處理。
3、預設情況下,標準的Spring MVC異常會通過DefaultHandlerExceptionResolver來處理。

六、程式碼實現

https://gitee.com/huan1993/spring-cloud-parent/tree/master/springboot/springboot-exception-handler

七、參考文件

參考文件

相關文章