はじめに
筆者はSIerでエンジニアをしています。
今日はexception handlerについてまとめてみます。
- はじめに
- exception handlerとは
- まずはexceptionが発生するAPIを用意する
- てことでexception発生時の処理を実装する
- exception handlerを実装する
- それぞれのexceptionをそれぞれ処理する。
- それでは全部のexceptionのhandlerを実装しないといけないのか?
- まとめ
exception handlerとは
アプリケーションから発生した例外に対する処理を一箇所で管理できます。
また、それぞれのexceptionに対して、処理を実装できます。
実際に見てみましょう。
まずはexceptionが発生するAPIを用意する
なんらかexceptionが発生するAPIがあるとします。
この実装だと、エラーがSpringBootデフォルトのハンドリングが行われます。
そして、Whitelabel Error Page
のようなものがレスポンスとして返されます。
あまり嬉しくないですね。
@RestController
@RequestMapping("/api")
public class SampleApi2 {
@GetMapping("/hello2")
public ResponseEntity<String> getHello() {
// エラーが発生する処理
doSomething();
return ResponseEntity.ok("Hello, World!");
}
private void doSomething() {
throw new RuntimeException();
}
}
てことでexception発生時の処理を実装する
try-catchを使用して、エラー時の処理を実装します。
これでしっかりアプリケーションとして、例外の制御もできるようになりました。
@RestController
@RequestMapping("/api")
public class SampleApi2 {
@GetMapping("/hello2")
public ResponseEntity<String> getHello() {
try {
// エラーが発生する処理
doSomething();
return ResponseEntity.ok("Hello, World!");
} catch (Exception e) {
return ResponseEntity.status(500).body("Internal Server Error");
}
}
private void doSomething() {
throw new RuntimeException();
}
}
ただし、このAPIがたくさんあったら、いちいち面倒ですね。
ここでexception handlerです。
exception handlerを実装する
@RestControllerAdvice
と@ExceptionHandler
を使用して、exception handlerを実装しましょう。
@RestControllerAdvice
自体は、ExceptionHandler以外にも使用します。今回はexception handlerの実装を目的として使用します。
@RestControllerAdvice
public class SampleExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
// エラーが発生した場合の処理
return ResponseEntity.status(500).body("Internal Server Error");
}
}
これによって、controllerでの個別実装が不要になります。
@GetMapping("/hello3")
public ResponseEntity<String> getHello3() {
// エラーが発生する処理
doSomething();
return ResponseEntity.ok("Hello, World!");
}
ということで、APIが増えても一箇所にまとめられて良いですね。
それぞれのexceptionをそれぞれ処理する。
さて、それでも発生する例外は一種類であありません。各exceptionにそれぞれ処理を実装したい場合は、アノテーションに渡すクラスをそれぞれ記載します。
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
// エラーが発生した場合の処理
return ResponseEntity.status(500).body("Internal Server Error");
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {
// エラーが発生した場合の処理
return ResponseEntity.status(500).body("Runtime Error");
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
// エラーが発生した場合の処理
return ResponseEntity.status(400).body("Bad Request");
}
で、(絶対ないですが)こんなAPIがあったとします。
// runtime exceptionを発生させるAPI
@GetMapping("/hello4")
public ResponseEntity<String> getHello4() {
throw new RuntimeException();
}
// illegal argument exceptionを発生させるAPI
@GetMapping("/hello5")
public ResponseEntity<String> getHello5() {
throw new IllegalArgumentException();
}
この結果、各APIで発生した例外が、各handlerで捕捉されます。
API エンドポイント | 発生する例外 | 処理するハンドラー | HTTPステータス | レスポンスボディ |
---|---|---|---|---|
/api/hello4 | RuntimeException | handleRuntimeException | 500 | "Runtime Error" |
/api/hello5 | IllegalArgumentException | handleIllegalArgumentException | 400 | "Bad Request" |
その他 | Exception | handleException | 500 | "Internal Server Error" |
独自例外も含めて個別に処理を実装できていい感じですね。
それでは全部のexceptionのhandlerを実装しないといけないのか?
と思いますが、そうではありません、java.lang.Exception
に対するhandlerを実装すれば、他のhandler@ExceptionHandler
で捕捉されないものが、全て捕捉されます。
これはJavaの例外クラスの継承構造によるものです。すべての例外はjava.lang.Exception
クラスを継承しています。
Exception(↑ここが頂点)
├── RuntimeException
│ └── IllegalArgumentException
├── IOException
└── その他の例外クラス
このように、全ての例外はException
クラスの子クラスになっているため、@ExceptionHandler(Exception.class)
と記述することで、他のhandlerで捕捉されなかった全ての例外をキャッチすることができます。
なので、よく発生する例外は個別にhandlerを実装し、想定外の例外はException
のhandlerで捕捉する、という実装で十分なのです。
継承とか、全ての例外はExceptionの子クラスとか、その辺はJavaの言語として理解を進めましょう。
まとめ
今回はexception handlerについてまとめました。始めた時は個別にエラーハンドリングを実装しがちだったのですが、この仕組みはとても便利ですね。
今回は簡単な例でしたが、エラー用のログを出力したり、いろいろできそうです。
では今回は以上です。