글로벌 예외 처리(Global Exception Handling)
Spring에서 글로벌 예외 처리(Global Exception Handling)는 애플리케이션 전역에서 발생하는 예외를 한 곳에서 처리할 수 있도록 돕는 중요한 기능이다. 이와 관련하여 @RestControllerAdvice와 @ExceptionHandler는 글로벌 예외 처리의 핵심 요소로 작동하며, 이를 통해 예외 처리 로직을 중앙 집중화하고 코드 중복을 줄일 수 있다. 이를 통해 다음과 같은 이점을 얻을 수 있다.
글로벌 예외 처리는 모든 컨트롤러에서 발생하는 예외를 중앙에서 관리하기 위한 설계 패턴이다.
- 중복 코드 제거: 모든 컨트롤러에서 중복적으로 작성할 필요 없음.
- 유지보수성 향상: 예외 처리 로직이 한 곳에 모여 있어 수정 및 관리가 용이.
- 일관된 응답 처리: 클라이언트에 전달되는 오류 응답의 형식을 통일할 수 있음.
Spring에서는 @RestControllerAdvice를 활용하여 글로벌 예외 처리 핸들러를 구현한다,
먼저, Spring에서 예외 처리 방식인 @ExceptionHandler와 @RestControllerAdvice는 각각의 특징과 활용 방법을 알아보자.
@ExceptionHandler
@ExceptionHandler는 특정 컨트롤러 내에서 발생한 예외를 처리하는 데 사용된다.
메서드 수준에서 선언되며, 해당 컨트롤러의 요청 처리 중 발생한 특정 예외에 반응한다.
특징
- 컨트롤러 클래스 내부에서만 동작하며, 이 클래스에서 발생한 예외만 처리한다.
- 단일 컨트롤러에 국한되므로, 특정 컨트롤러만의 예외 처리 요구사항이 있을 때 적합하다.
- 예외가 발생하면 Spring이 해당 예외와 매핑되는 @ExceptionHandler 메서드를 호출한다.
동작 원리
- 클라이언트 요청 처리 중 예외 발생 시 Spring은 컨트롤러 내의 @ExceptionHandler를 탐색합니다.
- 예외 타입과 메서드에 정의된 예외 타입이 일치하면 해당 메서드가 호출됩니다.
구현 예제
@RestController
public class MyController {
@GetMapping("/example")
public String example() {
throw new IllegalArgumentException("잘못된 요청입니다."); // 예외 발생
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Error: " + e.getMessage());
}
}
- /example 엔드포인트 호출 시 IllegalArgumentException이 발생한다.
- Spring은 IllegalArgumentException에 매핑된 @ExceptionHandler 메서드를 호출한다.
- 응답으로 상태 코드 400과 함께 오류 메시지가 반환된다.
@RestControllerAdvice
Spring 애플리케이션의 모든 @RestController에서 발생하는 예외를 처리하기 위해 사용된다.
전역 예외 처리기로 동작하며, 아래와 같은 방법으로 특정 패키지나 클래스에 범위를 제한할 수도 있다.
@RestControllerAdvice(basePackages = ...)).
특징
- 애플리케이션 전체에서 예외를 처리할 수 있다.
- 여러 컨트롤러에서 발생하는 공통 예외를 한 곳에서 관리할 수 있다.
- 보다 선언적이고 구조화된 예외 처리 방식이다.
- 특정 패키지나 클래스만 대상으로 설정할 수 있다.
구현 예제
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleRuntimeException(RuntimeException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("알 수 없는 서버 에러가 발생했습니다.");
}
}
- 애플리케이션 내의 모든 컨트롤러에서 IllegalArgumentException이나 RuntimeException이 발생하면 GlobalExceptionHandler의 해당 메서드가 호출된다.
내부 작동 방식
- 클라이언트 요청 처리 중 예외 발생
- 클라이언트가 API를 호출하고, 컨트롤러에서 요청을 처리하는 도중 예외가 발생
- 예외 탐색 순서
- Spring은 먼저 컨트롤러 내부에서 @ExceptionHandler 메서드를 탐색한다.
- 해당 컨트롤러에 적합한 핸들러가 없으면 전역 핸들러(@RestControllerAdvice)를 탐색한다.
- 예외 핸들러 실행
- 매핑된 예외 핸들러가 발견되면 이를 실행하고, 결과로 생성된 응답 객체(ResponseEntity)를 반환한다.
비교
특징 | @ExceptionHandle | @RestControllerAdvice |
적용 범위 | 단일 컨트롤러 내에서만 동작 | 전역적으로 모든 컨트롤러에 적용 |
사용 위치 | 컨트롤러 클래스 내부 | 별도의 클래스에서 정의 |
공통 처리 | 공통 처리가 어렵고, 중복 코드 발생 가능 | 공통된 예외를 한 곳에서 관리 가능 |
복잡성 | 간단한 예외 처리에 적합 | 여러 예외를 통합적으로 관리할 때 적합 |
패키지/클래스 제한 | 제한 불가 | 특정 패키지 또는 클래스에 범위 지정 가능 |
예외 처리 우선순위 | @ExceptionHandler 메서드가 우선 적용 | 컨트롤러에 @ExceptionHandler가 없으면 적용 |
글로벌 예외 처리 구현
@RestControllerAdvice
public class GlobalExceptionHandler {
// 특정 예외 처리
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Invalid request: " + ex.getMessage());
}
// 모든 예외 처리
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGenericException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An unexpected error occurred: " + ex.getMessage());
}
}
- 모든 컨트롤러에서 발생하는 예외가 글로벌 핸들러로 전달된다.
- 발생한 예외의 타입에 따라 매핑된 @ExceptionHandler 메서드가 호출된다.
- 응답 객체를 생성하여 클라이언트에 반환한다.
예외 응답 객체 커스터마이징
public class ErrorResponse {
private String errorCode;
private String message;
private LocalDateTime timestamp;
public ErrorResponse(String errorCode, String message) {
this.errorCode = errorCode;
this.message = message;
this.timestamp = LocalDateTime.now();
}
// Getters and setters
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex) {
ErrorResponse response = new ErrorResponse("ERR-400", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
ErrorResponse response = new ErrorResponse("ERR-500", "An unexpected error occurred");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
- 글로벌 예외 처리에서는 응답 객체를 구조화하여 클라이언트가 예외를 더 명확히 이해할 수 있도록 한다.
응답 결과 예시
// 클라이언트 요청에 IllegalArgumentException 발생 시
{
"errorCode": "ERR-400",
"message": "Invalid parameter",
"timestamp": "2024-12-11T12:34:56"
}
// 클라이언트 요청에 예기치 않은 예외 발생 시
{
"errorCode": "ERR-500",
"message": "An unexpected error occurred",
"timestamp": "2024-12-11T12:34:56"
}
@RestControllerAdvice 사용 시 고려사항
- 적용 범위 관리: 전역적으로 적용되므로, 모든 예외를 처리하지 않도록 범위를 명확히 설정하거나 특정 예외를 구체적으로 처리해야 한다.
- HTTP 상태 코드 반환: 적절한 상태 코드와 메시지를 반환하여 클라이언트가 예외 상황을 명확히 이해할 수 있도록 해야 한다.
- 응답 일관성: 클라이언트와 협의된 공통 응답 포맷을 사용하여 오류 응답을 통일한다.
'Spring' 카테고리의 다른 글
[Spring] Security JWT (0) | 2024.11.10 |
---|---|
[Spring] 세션, 토큰, 쿠키 (0) | 2024.11.08 |
[Spring] @Profile (0) | 2024.11.04 |
[Spring] 스프링 AOP - 실무 주의사항 (1) | 2024.09.25 |
[Spring] 스프링 AOP - 로그 출력 AOP 만들기 (0) | 2024.09.24 |