Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

Spring Boot 例外処理/エラーハンドリングメモ

スポンサードリンク

Spring Bootでは例外処理はthrowしてしまえばわりとよしなにやってくれるが… それでも考えるべきことはある。 場合によっては例外処理をあまり使わず、オブジェクトの戻り値による処理をすることもある。

例外処理のパターン@Controller編

基本的には

  1. @Controllerに個別で@ExceptionHandler
  2. @ControllerAdvice@ExceptionHandler おすすめ
  3. Spring BootのError Controller

3番目は知らなかった 1番目と2番目はよく使うと思う 今回はError Controllerを紹介します

@Slf4j
@ControllerAdvice("com.example.controllers.foo")
public class GlobalControllerAdvice {
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e, HttpServletResponse response, Model model) {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        log.error("Error occurred.", e);
        model.addAttribute("errorMessage", e.getMessage());
        
        return "foo/error";
    }
}

プレゼンテーション層、404エラーなど全ての例外を補足できない&status codeがセットされないという問題がある

ErrorControllerを使う

Spring MVCでは次の順に呼ばれるとのこと

  1. ExceptionHandlerExceptionResolver(@ExceptionHandler)
  2. ResponseStatusExceptionResolver(@ResponseStatus)
  3. DefaultHandlerExceptionResolver(Spring MVCで起きた例外を処理)

ErrorControllerはこれらが適用された後に呼ばれる

例外処理のパターン@RestController編

  1. @RestControllerに個別で@ExceptionHandler
  2. @RestControllerAdvice@ExceptionHandler
  3. @ResponseStatusを付与した例外をThrow

今回は2つ目の@ExceptionHandlerのパターンを紹介します。

ExceptionHandlerを使う

@Data
public class ErrorResponse {
    private String keyName;
    private String keyValue;
    private String message;

    public ErrorResponse(String keyName, String keyValue, String message) {
        this.keyName = keyName;
        this.keyValue = keyValue;
        this.message = message;
    }
    public ResponseEntity<ErrorResponse> createResponse(HttpStatus status) {
        return new ResponseEntity<ErrorResponse>(this, status);
    }
    public static ResponseEntity<ErrorResponse> createResponse(BadRequestException e) {
        return new ResponseEntity<ErrorResponse>(
                new ErrorResponse(e.getKeyName(), e.getKeyValue(), e.getMessage())
                    ,HttpStatus.BAD_REQUEST);
    }
}

自前例外(CreateResponseの引数になる)

@Data
public class BadRequestException extends Exception {

    private String keyName;
    private String keyValue;
    private String message;

    public BadRequestException(String keyName, String keyValue, String message) {
        this.keyName = keyName;
        this.keyValue = keyValue;
        this.message= message;
    }

}

例外ハンドラ

@RestControllerAdvice
public class BadRequestExceptionHandler {

    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<ErrorResponse> handleException(HttpServletRequest req, BadRequestException e) {
        return ErrorResponse.createResponse(e);
    }
    
}

なお、 SpringBootの@RestControllerで例外処理をする #SpringBoot - Qiita によると、 既に用意してあるhandlerを継承したほうが良いかもしれない。

@RestControllerAdvice
public class BadRequestExceptionHandler extends ResponseEntityExceptionHandler {

    // 自分で定義したBadRequestExceptionをキャッチする
    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<?> handleMyException(BadRequestException ex, WebRequest request) {
        HttpHeaders headers = new HttpHeaders();
        return super.handleExceptionInternal(ex, ErrorResponse.createResponse(ex), headers, HttpStatus.BAD_REQUEST, request);
    }
...snip...

ちなみにですが、 ResponseEntity<?>ResponseEntity<Object>ResponseEntity<? extends Object>とだいたい一緒です。。 つまり何らかのクラスってことです。 細かいことは境界ワイルドカード型(bounded wildcard type)で調べるとわかるかと思います。 Springつかうとこれじゃないと返せないケースがあります。

その他リンク

https://www.baeldung.com/exception-handling-for-rest-with-spring https://www.slideshare.net/shintanimoto/spring-boot10 https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html