きり丸の技術日記

技術・エンジニアのイベント・資格等はこちらにまとめる予定です

【Spring】共通的かつ特別なハンドリングを行いたかった

共通的かつ一部コントローラークラスのみ特別なハンドリングをする方法を残します。

環境

共通的なコントローラのハンドリング

RestControllerAdviceというアノテーションを付けると、全コントローラーを共通的にハンドリングすることができる。
ついでに、ResponseEntityExceptionHandlerをつけると、Springの基本のハンドリングができるようになるので、幸せになれると思います。

@RestControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
}

部分的なコントローラのハンドリング

そして、さらに共通的かつ特別なAPIのエラーをハンドリングをしたい時がある。(ないかも)
その時は、RestControllerAdviceにassignableTypesに目的のControllerを指定するといい。
仕事で使用していた時は、Orderを付けて優先度を高めることで、目的のハンドリングができるようになりました。 ただし、このやり方は基本的には不要だと思います。
AOP使うとどこでハンドリングされるかわからなくなりがちで、保守性がガンガン落ちていきます。  

@RestControllerAdvice(assignableTypes = {AnimalsRestController.class, GamesRestController.class})
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AnimalsExceptionHandler{

  @ExceptionHandler(BindException.class)
  public ResponseEntity<Object> handleRundtimeException(BindException ex, WebRequest request) {
    return new ResponseEntity<>("tokubetu",HttpStatus.BAD_REQUEST);
  }
}

特別なコントローラのハンドリング

特別な理由が無い限り、各コントローラーのExceptionHandlerでキャッチしたほうがわかりやすいと思います。
後述しますが、優先度的にもこちらが優先されます。

@RestController
public class GamesRestController {
  @ExceptionHandler(BindException.class)
  public ResponseEntity<Object> handleRundtimeException(BindException ex, WebRequest request) {
    return new ResponseEntity<>("gameException", HttpStatus.BAD_REQUEST);
  }
}

優先度

各コントローラーのExceptionHandler -> RestControllerAdvices(優先度順)
です。
また、各コントローラーのExceptionHandlerでキャッチした後、適当にExceptionをthrowsしても共通的なRestControllerAdvicesに遷移しませんでした。
なので、各コントローラー内でハンドリングするのであれば、キャッチした後にきっちり例外が起こらないようにする必要があります。
BadRequestのパラメータを加工してDBにセットするロジックを書いてたら、SystemExceptionが発生したのに、返却statusCodeは500にならずに400のままでした…。

もし、ハンドリングをキャッチした後に、別のハンドラーにキャッチさせるやり方を知っている方がいれば教えていただきたいです。

まとめ

共通的かつ独自的な例外は、Orderdを付けることで書けなくはない。
ただし、保守性は低くなるので、各コントローラーのExceptionHandlerでキャッチするようにした方がいいと思います。