Spring에서 커스텀 응답 처리하기: BaseResponse와 ControllerAdvice 활용법

민준·2025년 3월 22일

Spring Boot

목록 보기
2/3
post-thumbnail

1. BaseResponse : 커스텀 코드를 갖는 반환 클래스

1.1. BaseResponse를 왜 사용하는가?

Spring에서 기본적으로 제공하는 ResponseEntity<T>를 사용하면 HTTP 상태 코드와 함께 응답을 보낼 수 있지만 내부 API를 설계할 때는 다양한 이유로 커스텀 응답 코드를 사용해야 하는 경우가 있음

BaseResponse 사용이 필요한 경우

  • 내부 API 설계
    HTTP 상태 코드만으로 내부 API의 모든 상황을 표현하기 어려움.
    → 특정한 비즈니스 로직에 맞는 상태 코드를 따로 정의할 필요가 있음.
    → 예: A01 (타입 오류), A02 (입력 오류), B01 (외부 서버 통신 오류) 등.

  • 외부 Open API 설계
    Open API에서는 HTTP 상태 코드를 엄격하게 준수해야 하지만, 내부적으로도 더 세분화된 응답 처리가 필요할 수 있음.
    BaseResponse를 활용해 추가적인 상태 코드와 메시지를 전달.

  • 클라이언트(프론트엔드)와의 효율적인 연동
    프론트엔드는 응답 코드가 200 OK일 경우 내부 데이터를 신뢰하고 로직을 처리하는 경우가 많음.
    → 예외 발생 시에도 200 OK로 응답하되, 응답 데이터에 실패 상태를 포함하여 처리 가능.


1.2. BaseResponse 구현 방법

BaseResponse는 보통 아래와 같은 필드를 가집니다.

public class BaseResponse<T> {
    private String code; // 커스텀 응답 코드 (ex. A01, B02 등)
    private String message; // 응답 메시지
    private T data; // 실제 응답 데이터

    // 성공 응답을 위한 정적 메서드
    public static <T> BaseResponse<T> success(T data) {
        return new BaseResponse<>("200", "요청 성공", data);
    }

    // 실패 응답을 위한 정적 메서드
    public static <T> BaseResponse<T> error(String code, String message) {
        return new BaseResponse<>(code, message, null);
    }

    // 생성자 (private으로 숨김)
    private BaseResponse(String code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    // Getter 메서드 추가
    public String getCode() { return code; }
    public String getMessage() { return message; }
    public T getData() { return data; }
}

1.3. BaseResponse 적용 예시

성공 응답

@GetMapping("/user/{id}")
public ResponseEntity<BaseResponse<User>> getUser(@PathVariable Long id) {
    User user = userService.findById(id)
            .orElseThrow(() -> new CustomException("A02", "사용자를 찾을 수 없습니다."));
    
    return ResponseEntity.ok(BaseResponse.success(user));
}
  • BaseResponse.success(user)를 사용하여 성공 응답을 생성
  • HTTP 상태 코드는 200 OK, 응답 데이터는 BaseResponse<User> 형태로 반환됨

실패 응답

@ExceptionHandler(CustomException.class)
public ResponseEntity<BaseResponse<Void>> handleCustomException(CustomException e) {
    return ResponseEntity.ok(BaseResponse.error(e.getCode(), e.getMessage()));
}
  • 예외가 발생하면 HTTP 응답은 200 OK로 보내되, BaseResponse 내부에 실패 코드와 메시지를 포함
  • 프론트엔드는 BaseResponse.code 값을 확인하여 에러 여부를 판단 가능

2. @ControllerAdvice : 예외 처리 책임 위임

2.1. @ControllerAdvice가 필요한 이유

일반적으로 컨트롤러 내부에서 예외 처리를 할 때 try-catch를 사용하는 방식은 유지보수성이 떨어지고 중복 코드가 발생하기 쉽습니다.

이를 해결하기 위해 Spring은 @ControllerAdvice를 제공하며, 공통적인 예외 처리를 한 곳에서 관리할 수 있도록 합니다.


2.2. @ControllerAdvice 적용 방법

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(CustomException.class)
    public ResponseEntity<BaseResponse<Void>> handleCustomException(CustomException e) {
        return ResponseEntity.ok(BaseResponse.error(e.getCode(), e.getMessage()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<BaseResponse<Void>> handleGlobalException(Exception e) {
        return ResponseEntity.ok(BaseResponse.error("500", "서버 내부 오류가 발생했습니다."));
    }
}
  • @RestControllerAdvice : @ControllerAdvice + @ResponseBody 기능을 함
  • @ExceptionHandler(CustomException.class) : CustomException 발생 시 공통적으로 처리
  • @ExceptionHandler(Exception.class) : 예상하지 못한 예외를 처리

2.3. @RestControllerAdvice = @ControllerAdvice + @ResponseBody

@RestControllerAdvice@ControllerAdvice와 다르게 모든 응답을 JSON 형태로 변환

@ControllerAdvice
public class MyControllerAdvice {
    @ExceptionHandler(Exception.class)
    public BaseResponse<Void> handleException(Exception e) {
        return BaseResponse.error("500", "서버 내부 오류");
    }
}

위 코드는 @ResponseBody가 없어 JSON으로 변환되지 않고, HTML 오류 페이지가 반환될 수 있음.

이를 해결하려면 @ResponseBody를 추가하거나 @RestControllerAdvice를 사용해야 합니다.


3. 전체적인 흐름 정리

  1. BaseResponse
    → 커스텀 상태 코드와 메시지를 포함하는 응답 클래스.
  2. BaseResponse.success(), BaseResponse.error()
    → 정적 팩토리 메서드로 응답을 생성.
  3. @RestControllerAdvice
    → 컨트롤러에서 발생하는 예외를 전역적으로 처리.
  4. @ExceptionHandler(CustomException.class)
    → 비즈니스 로직에서 발생하는 특정 예외를 핸들링.
  5. @ExceptionHandler(Exception.class)
    → 모든 예외를 처리하여 예측하지 못한 오류에도 일관된 응답 제공.

0개의 댓글