Gobble up pudding

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

MENU

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

スポンサードリンク

f:id:fa11enprince:20190306032659j:plain

Spring Bootでは例外処理はthrowしてしまえばわりとよしなにやってくれるが… それでも考えるべきことはある。 場合によっては例外処理をあまり使わず、オブジェクトの戻り値による処理をすることもある。 と、去年の1月からSpring Bootを使い始めてSpringの良さがわかり、Springの深いところに潜り始めた。 気になったところをメモします。 気が向いたら更新しようかと思います。 雑にメモしていたので、気づいた誤りを修正しました。

全てここを参考にさせていただいています。 いろいろ学びが多く、とても良い資料です。 speakerdeck.com

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

基本的には

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

3番目は知らなかった 1番目と2番目はよく使うと思う

@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

qiita.com

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> getException(HttpServletRequest req, BadRequestException e){
        return ErrorResponse.createResponse(e);
    }
    
}

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

@RestControllerAdvice
public class BazExceptionHandler extends ResponseEntityExceptionHandler {

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

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

stackoverflow.com

その他リンク

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