Spring Bootでは例外処理はthrowしてしまえばわりとよしなにやってくれるが… それでも考えるべきことはある。 場合によっては例外処理をあまり使わず、オブジェクトの戻り値による処理をすることもある。
例外処理のパターン@Controller編
基本的には
@Controller
に個別で@ExceptionHandler
@ControllerAdvice
で@ExceptionHandler
おすすめ- 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では次の順に呼ばれるとのこと
- ExceptionHandlerExceptionResolver(
@ExceptionHandler
) - ResponseStatusExceptionResolver(
@ResponseStatus
) - DefaultHandlerExceptionResolver(
Spring MVC
で起きた例外を処理)
ErrorController
はこれらが適用された後に呼ばれる
例外処理のパターン@RestController編
@RestController
に個別で@ExceptionHandler
@RestControllerAdvice
で@ExceptionHandler
@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