導入
前レッスンで、バリデーションに失敗すると例外が飛ぶと説明しました。この例外をそのまま放置すると、クライアントには内部のスタックトレースを含む生々しいエラー画面が返ってしまいます。これは避けたい事態です。
説明
特定のControllerの中だけで例外を処理したいなら、そのControllerに @ExceptionHandler メソッドを追加します。
@RestController
class UserApiController {
// このController内で MethodArgumentNotValidException が発生したらここが呼ばれる
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldError().getDefaultMessage();
return ResponseEntity.badRequest().body(new ErrorResponse(message));
}
}
しかし、同じような例外処理をControllerごとにコピペするのは非効率です。アプリ全体で共通のエラー処理をまとめたい場合は、@ControllerAdvice を付けた専用クラスを1つ用意します。
@RestControllerAdvice // @ControllerAdvice + @ResponseBody
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldError().getDefaultMessage();
return ResponseEntity.badRequest().body(new ErrorResponse(message));
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(UserNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(ex.getMessage()));
}
@ExceptionHandler(Exception.class) // 上のどれにも当てはまらない、想定外の例外
public ResponseEntity<ErrorResponse> handleOther(Exception ex) {
return ResponseEntity.internalServerError()
.body(new ErrorResponse("サーバー内部でエラーが発生しました"));
}
}
graph TD Any["アプリ内のどのControllerで<br/>例外が発生しても"] --> Advice["@RestControllerAdvice<br/>のクラスが横断的に受け止める"] Advice --> R1["バリデーション例外<br/>→ 400 Bad Request"] Advice --> R2["未検出の例外<br/>→ 404 Not Found"] Advice --> R3["想定外の例外<br/>→ 500 Internal Server Error"]
@ControllerAdvice を1つ用意しておくことで、「アプリ全体でどんな例外が、どんなHTTPステータスコードとエラーメッセージになって返るか」を1か所に集約できます。各Controllerは業務ロジックに専念でき、エラー処理を気にする必要がなくなります。
やってみよう
「注文しようとした商品の在庫が足りない」という業務エラーを表す OutOfStockException を新しく作るとしたら、@RestControllerAdvice にどんなハンドラを追加し、どのHTTPステータスコード(400? 404? 409?)を返すのがふさわしいか考えてみましょう。