こんにちは!今回は、Spring Frameworkで例外処理に使用されるアノテーション、@ExceptionHandlerについて詳しく解説します。@ExceptionHandlerを使えば、アプリケーション内で発生するさまざまな例外を一箇所でキャッチし、適切に処理できるようになります。
@ExceptionHandlerとは?
@ExceptionHandlerとは、
「特定の例外が発生したときに、その例外を処理するメソッドを定義する」
ことのできるアノテーションです。
アプリケーションが例外を投げた場合、通常はエラーレスポンスが返されますが、@ExceptionHandlerを使用することで、例外に応じたカスタムレスポンスを返したり、ログを記録したりすることが可能です。これにより、アプリケーションのエラーハンドリングを統一的かつ柔軟に管理できます。
基本的な使い方
まずは、基本的な@ExceptionHandlerの使い方を見てみましょう。ユーザーが存在しない場合にUserNotFoundException
をキャッチしてカスタムエラーメッセージを返す例を紹介します。
サーバー側のコード
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id)
.orElseThrow(() -> new UserNotFoundException("User not found with id: " + id));
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
}
この例では、/users/{id}
エンドポイントでユーザーを取得し、存在しない場合はUserNotFoundException
を投げています。そして、@ExceptionHandler
でこの例外をキャッチし、404ステータスと共にエラーメッセージを返しています。
@ExceptionHandlerの使い方【応用編】
@ExceptionHandlerを活用すれば、さらに高度な例外処理も可能です。ここでは、少し複雑なシナリオを見ていきましょう。
1. 複数の例外を処理する
@ExceptionHandler
は複数の例外に対しても対応できます。同じハンドラで複数の例外を処理したい場合、以下のように配列で例外クラスを指定します。
@ExceptionHandler({ UserNotFoundException.class, IllegalArgumentException.class })
public ResponseEntity<String> handleMultipleExceptions(RuntimeException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Error: " + ex.getMessage());
}
このコードでは、UserNotFoundException
とIllegalArgumentException
の両方をキャッチして処理しています。どちらの例外が発生しても、400エラーレスポンスが返されます。
2. カスタム例外クラスを作る
より具体的な例外処理を行いたい場合、カスタム例外クラスを作成し、専用の@ExceptionHandler
を用意することができます。
public class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
}
@ExceptionHandler(CustomException.class)
public ResponseEntity<String> handleCustomException(CustomException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
}
このコードでは、CustomException
という独自の例外を定義し、発生時に500 Internal Server Errorとともにカスタムメッセージを返しています。これにより、特定の業務ロジックに応じた柔軟なエラーハンドリングが可能です。
3. 共通の例外ハンドラを作る
アプリケーション全体で同じエラーハンドリングを適用したい場合、@ControllerAdvice
を使って共通の例外ハンドラを作成することができます。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGlobalException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred: " + ex.getMessage());
}
}
@ControllerAdvice
を使用することで、アプリケーション内のどのコントローラからでも例外が投げられた場合に、この共通のハンドラで処理されるようになります。例外が発生したときにログを残したり、共通のエラーページを表示したりするのに便利です。
4. バリデーションエラーを処理する
フォーム入力やリクエストデータのバリデーションエラーをキャッチすることも可能です。以下の例では、MethodArgumentNotValidException
を使ってリクエストのバリデーションエラーを処理します。
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException ex) {
String errorMessage = ex.getBindingResult().getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining(", "));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Validation failed: " + errorMessage);
}
このコードでは、バリデーションエラーが発生した場合、エラーメッセージを抽出して400ステータスと共に返しています。
5. ログを記録する
エラー発生時にログを残しておくことは、トラブルシューティングの際に非常に役立ちます。以下の例では、Logger
を使って例外の詳細をログに記録しています。
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
logger.error("An unexpected error occurred: ", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Something went wrong.");
}
このコードでは、例外が発生したときにエラーメッセージとスタックトレースをログに記録し、ユーザーにはシンプルなエラーメッセージを返しています。
@ExceptionHandlerの引数
@ExceptionHandler
のメソッドには、いくつかの引数を渡すことができます。
例外オブジェクト@ExceptionHandler
のメソッドには、発生した例外オブジェクトが自動的に渡されます。これにより、例外の詳細な情報を参照したり、エラーメッセージを取得したりできます。
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
HttpServletRequestやHttpServletResponse
必要に応じて、HttpServletRequest
やHttpServletResponse
を引数として受け取ることも可能です。これにより、リクエストやレスポンスに対して直接操作を行うことができます。
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex, HttpServletRequest request) {
String requestUrl = request.getRequestURI();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Error occurred while accessing: " + requestUrl);
}
おわりに
@ExceptionHandler
を使えば、アプリケーション全体のエラーハンドリングを一元管理でき、コードの可読性やメンテナンス性が向上します。今回は、基本的な使い方から応用例まで紹介しましたので、ぜひ実際のプロジェクトで活用してみてください!
もっと詳しく知りたい方は、Spring公式ドキュメントを見てみてくださいね。
他の記事が見たい方はこちら!↓↓↓