본문 바로가기

Spring

[Spring] 예외 처리 방식

글로벌 예외 처리(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의 해당 메서드가 호출된다.

내부 작동 방식

  1. 클라이언트 요청 처리 중 예외 발생
    • 클라이언트가 API를 호출하고, 컨트롤러에서 요청을 처리하는 도중 예외가 발생
  2. 예외 탐색 순서
    • Spring은 먼저 컨트롤러 내부에서 @ExceptionHandler 메서드를 탐색한다.
    • 해당 컨트롤러에 적합한 핸들러가 없으면 전역 핸들러(@RestControllerAdvice)를 탐색한다.
  3. 예외 핸들러 실행
    • 매핑된 예외 핸들러가 발견되면 이를 실행하고, 결과로 생성된 응답 객체(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 사용 시 고려사항

  1. 적용 범위 관리: 전역적으로 적용되므로, 모든 예외를 처리하지 않도록 범위를 명확히 설정하거나 특정 예외를 구체적으로 처리해야 한다.
  2. HTTP 상태 코드 반환: 적절한 상태 코드와 메시지를 반환하여 클라이언트가 예외 상황을 명확히 이해할 수 있도록 해야 한다.
  3. 응답 일관성: 클라이언트와 협의된 공통 응답 포맷을 사용하여 오류 응답을 통일한다.

 

'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