きり丸の技術日記

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

共通的にExceptionをキャッチして、フロントに返却する(RestControllerAdvise)

きり丸アドベントカレンダー2020の17記事目です。


エラーが発生した時に、共通的にExceptionをキャッチできると、フロントに返却するレスポンスのHttpStatusやBodyを共通化できます。その共通化にはRestControllerAdviseを使用します。

それぞれ適切なExceptionを出すことにより、トランザクション系のデータが存在しないときはHttpStatus:404、マスタ系のデータが存在しないときはHttpStatus:500を返却したい、というような振り分けをできます。

ぜひ覚えてみましょう。

ゴール

  • Exceptionを共通的にキャッチする

環境

  • Java
    • 15
  • org.springframework.boot:spring-boot-starter-Web
    • 2.4.0
  • lombok

手順

Exceptionを拡張する


Exceptionを拡張しておくと、共通的にキャッチするときに見分けがつけられます。そうでなくとも、ライブラリのExceptionをそのまま使用するとライブラリを変更した時にキャッチできなくなってしまいます。

ですので、基本的にライブラリのExceptionから自作Exceptionに翻訳してthrowするのがベターです。

// 自作Exceptionであることを示すException
public abstract class BusinessException extends RuntimeException {
}

さらに、キャッチしたあとにHttpStatus:404を返却するためのExceptionを作成します。HttpStatusごとにExceptionを作っておき、さらに意図があれば継承したExceptionを作成しましょう。

// HttpStatus:404を返却するためのException
public class NotFoundException extends BusinessException {
}

Exceptionをキャッチする仕組みを作る


@RestControllerAdviceを付与したうえで、ResponseEntityExceptionHandlerを継承します。継承しなくても動きますが複数の例外をキャッチしてくれるので、使っておいた方がいいでしょう。

今回は自作したNotFoundExceptionをキャッチしたうえでHttpStatus:404を返却します。@ExceptionHandlerにキャッチ用のExceptionのクラスを指定して、返却値のhandleExceptionInternalメソッドの第四引数にHttpStatus.NOT_FOUNDを指定します。

Bodyは意識していないので、「not found」の文字列だけを返却するようにしています。

@RestControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(NotFoundException.class)
  public ResponseEntity<Object> handleRundtimeException(NotFoundException ex, WebRequest request) {
    return super.handleExceptionInternal(ex, "not found", null, HttpStatus.NOT_FOUND, request);
  }
}

Exceptionを発生させるコードを書く


16日の記事ではデータが存在しないときは、return ResponseEntity.notFound().build();とControllerでHttpStatusを指定して返却していました。

それも悪くないですが、今回の仕組みを使用したいのでthrow new NotFoundException();に書き換えます。

この状態でTodoRestControllerTests.javaのテストを実行して失敗しなければ問題ありません。ちなみにキャッチできないようなExceptionはHttpStatus:500になるので、throw new RuntimeException();に書き換えたりして確認してみてください。

ファイル名:TodoRestController.java

  @GetMapping("/{userId}")
  public ResponseEntity<List<TodoDto>> get(@PathVariable String userId)  {

    final List<TodoDto> list = todoRepository.findList(userId);

    if (list.isEmpty()) {
      throw new NotFoundException(); // データが無いときに自作NotFoundException
    } else {
      return ResponseEntity.ok(list);
    }
  }

備考

こちらの記事もExceptionHandlerの記事なので、見ていただけるとうれしいです。

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

ソースコード

アドベントカレンダー17日目。
github.com

終わりに

シンプルにHttpStatusまとめて返却するだけの実装を紹介しましたが、Exceptionの拡張方法によってはログを出力したりレスポンスBodyを加工する等ができます。

RestControllerAdviseを使用しない場合は各Controllerで頑張ってExceptionをハンドリングする必要があります。必要であればキャッチすべきですが、そこまで頑張ることは多くないと思うので、楽してハンドリングしましょう。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

類似記事

きり丸アドベントカレンダー2020 adventar.org

きり丸のHerokuページ
https://kirimaru-todoapp.herokuapp.com/

18日目のアドベントカレンダーの記事 https://nainaistar.hatenablog.com/entry/2020/12/18/083000nainaistar.hatenablog.com

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

f:id:nainaistar:20201109133010p:plain