스프링 부트(Spring Boot)는 웹 애플리케이션에서 발생하는 예외를 관리하고 사용자에게 적절한 응답을 제공하기 위한 강력한 예외 처리 메커니즘을 제공합니다. 이 글에서는 @ExceptionHandler
와 @ControllerAdvice
를 활용하여 예외를 처리하는 방법을 단계별로 알아봅니다.
1. 예외 처리의 필요성
예외 처리는 다음과 같은 이유로 중요합니다:
- 사용자 경험 향상: 의미 있는 에러 메시지를 제공하여 사용자가 문제를 이해하도록 돕습니다.
- 보안 강화: 내부 시스템 정보를 사용자에게 노출하지 않도록 보호합니다.
- 유지보수성: 중앙화된 예외 처리를 통해 코드의 가독성과 유지보수성을 높입니다.
2. @ExceptionHandler로 개별 컨트롤러에서 예외 처리
@ExceptionHandler
는 특정 컨트롤러에서 발생한 예외를 처리하는 메서드에 적용됩니다.
사용 예제: 개별 컨트롤러 예외 처리
Controller:
@RestController
@RequestMapping("/products")
public class ProductController {
@GetMapping("/{id}")
public Product getProductById(@PathVariable Long id) {
// 예외를 강제로 발생시킴
throw new ProductNotFoundException("Product with ID " + id + " not found.");
}
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<String> handleProductNotFoundException(ProductNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
}
Custom Exception:
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException(String message) {
super(message);
}
}
설명:
@ExceptionHandler
를 사용하여 특정 예외(ProductNotFoundException
)를 처리합니다.- 예외 발생 시
handleProductNotFoundException
메서드가 호출되어 응답을 생성합니다.
3. @ControllerAdvice로 전역 예외 처리
@ControllerAdvice
는 애플리케이션 전역에서 발생하는 예외를 처리할 수 있는 메커니즘을 제공합니다. 모든 컨트롤러에 동일한 예외 처리를 적용할 때 유용합니다.
사용 예제: 전역 예외 처리
GlobalExceptionHandler:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<String> handleProductNotFoundException(ProductNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An unexpected error occurred: " + ex.getMessage());
}
}
설명:
@RestControllerAdvice
는 모든 컨트롤러의 예외를 전역적으로 처리합니다.- 특정 예외(
ProductNotFoundException
)와 일반적인 예외(Exception
)를 각각 처리합니다. - 처리 결과는 HTTP 상태 코드와 메시지로 반환됩니다.
4. @ExceptionHandler와 @ControllerAdvice의 비교
특징 | @ExceptionHandler | @ControllerAdvice |
---|---|---|
적용 범위 | 개별 컨트롤러 | 애플리케이션 전역 |
사용 목적 | 특정 컨트롤러에 한정된 예외 처리 | 모든 컨트롤러의 공통 예외 처리 |
구현 위치 | 컨트롤러 클래스 내부 | 별도의 클래스에서 관리 |
유지보수성 | 한정적이고 관리 대상이 많아질 수 있음 | 관리가 용이하고 재사용성이 높음 |
5. ResponseEntity와 커스텀 응답 생성
스프링의 ResponseEntity
를 활용하면 예외 응답의 상태 코드, 헤더, 메시지를 자유롭게 커스터마이징할 수 있습니다.
사용 예제: 응답 커스터마이징
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<ErrorResponse> handleProductNotFoundException(ProductNotFoundException ex) {
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
System.currentTimeMillis()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
}
ErrorResponse 객체:
public class ErrorResponse {
private int status;
private String message;
private long timestamp;
public ErrorResponse(int status, String message, long timestamp) {
this.status = status;
this.message = message;
this.timestamp = timestamp;
}
// Getters and Setters
}
결과 예시 (JSON):
{
"status": 404,
"message": "Product with ID 1 not found.",
"timestamp": 1732113600000
}
6. @ControllerAdvice에서 데이터 유효성 검증 에러 처리
스프링 부트는 데이터 검증 실패 시 MethodArgumentNotValidException
을 발생시킵니다. 이를 @ControllerAdvice
에서 처리하여 사용자에게 친절한 메시지를 제공할 수 있습니다.
사용 예제: 유효성 검증 에러 처리
UserForm 클래스:
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Email;
public class UserForm {
@NotEmpty(message = "사용자명을 입력하세요.")
private String username;
@Email(message = "유효하지 않은 이메일입니다.")
private String email;
// Getters and Setters
}
Controller:
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public String createUser(@Valid @RequestBody UserForm userForm) {
return "User created: " + userForm.getUsername();
}
}
GlobalExceptionHandler:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return ResponseEntity.badRequest().body(errors);
}
}
결과 예시 (JSON):
{
"username": "사용자명을 입력하세요.",
"email": "유효하지 않은 이메일입니다."
}
7. 주요 개념 요약
- @ExceptionHandler: 개별 컨트롤러에서 발생한 예외를 처리합니다.
- @ControllerAdvice: 애플리케이션 전역에서 발생한 예외를 처리합니다.
- ResponseEntity: 상태 코드와 커스텀 응답 메시지를 유연하게 조합할 수 있습니다.
- 데이터 검증 예외 처리:
@Valid
와BindingResult
를 통해 사용자 입력 에러를 처리합니다.
'SpringBoot' 카테고리의 다른 글
Spring Boot: 데이터 검증(Validation) (1) | 2024.11.22 |
---|---|
Spring Boot: 로깅(Logging) 구현하기 (1) | 2024.11.21 |
Spring Boot: Form 데이터 처리 (0) | 2024.11.19 |
Spring Boot: 스프링 MVC 패턴 (0) | 2024.11.18 |
Spring Boot: CRUD 기능 구현 (1) | 2024.11.17 |