Skip to content

Conversation

@Yujin1219
Copy link
Member

@Yujin1219 Yujin1219 commented Nov 3, 2025

#️⃣ 연관된 이슈

📝 미션 번호

7주차 Misson

📋 구현 사항

API 응답통일 image

global 패키지 안에 apiPayload 패키지를 만들어 응답 통일 코드를 작성하였다.
status 패키지를 만들어 에러나 성공 메세지에 대한 enum을 둔 파일을 넣어 분리하였다.

image

ReasonDTO와 ErrorReasonDTO는 각각 성공와 실패시 응답에 대한 dto이다. 컨트롤러나 예외 처리에서 일관된 응답을 위한 dto이다.
JavaBeans 규칙상 boolean 필드는 기본 게터가 isXxx()가 된다.
Lombok이 이렇게 게터를 만들면 Jackson은 is를 접두사로 보고 제거해, 결과적으로 속성 이름을 success로 해석하게 된다. (필드명이 isSuccess여도 JSON 키가 success로 나갈 수 있음)
이를 방지하기 위해 게터 명을getIsSuccess()로 두거나
@JsonProperty("isSuccess")로 키 이름을 고정하는 방식을 쓴다. 나는 여기서 게터명을 설정해주었다.

image

BaseCode는 성공 응답 코드를 표준화할대 구현하도록 하는 인터페이스 이다.
getReason과 getReasonHttpStatus 두 메서드 모두 ReasonDTO를 반환하지만 http상태를 포함하냐 안하냐에 따라 구분한 것이다.

image

해당 메서드들은 status 코드들에 오버라이딩 하여 구현
이제 이 값들을 가지고 ApiResponse를 채우고 공통 응답을 보내게 된다.
예외가 발생하였을 때 successstatus나 errorstatus가 주는 DTO를 꺼내 ApiResponse로 만들어 반환하게 된더ㅏ.

  • @JsonProperty("isSuccess")@JsonPropertyOrder키 이름과 순서를 보장
  • @JsonInclude(NON_NULL)result가 없을 때 필드를 아예 생략해 응답을 간결하게 만듦
ExceptionAdvice

validation()

image

ConstraintViolationException 는 제약 조건 위반 예외로, @Valid 가 아닌 메서드 파라미터 수준(@RequestParam, @PathVariable, @RequestHeader등)에서 검증 실패시 발생한다.

즉, 컨트롤러 메서드의 파라미터나 Pathvariable에 @NotNull, @SiZe 등을 붙혔을 때, 유효하지 않은 값이 들어오면 이 예외가 던져진다.

handleMethodArgumentNotValid()

/**
     * @Valid 어노테이션으로 인한 유효성 검사 실패 시 (주로 @RequestBody DTO) 이 핸들러가 호출
     * Spring의 기본 핸들러를 오버라이드
     */
    @Override
    public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatusCode status, WebRequest request) {

        // 유효성 검사에 실패한 필드와 에러 메시지를 저장할 Map
        Map<String, String> errors = new LinkedHashMap<>();
        // 예외 객체(e)의 BindingResult에서 모든 필드 에러(FieldErrors)를 가져와 스트림으로 처리
        e.getBindingResult().getFieldErrors().stream()
                .forEach(fieldError -> {
                    String fieldName = fieldError.getField(); // 에러가 발생한 필드 이름
                    String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse(""); // 에러에 설정된 기본 메세지
                    // Map에 필드 이름(key)과 에러 메시지(value)를 저장
                    // 만약 이미 동일한 필드 이름(key)이 존재하면, 기존 메시지 뒤에 새 메시지
                    errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage);
                });

        return handleExceptionInternalArgs(e,HttpHeaders.EMPTY,ErrorStatus.valueOf("_BAD_REQUEST"),request,errors);
    }

이 메서드는 MethodArgumentNotValidException 를 처리하는 메서드 이다.
MethodArgumentNotValidException@Valid 또는 @Validated 가 붙은 RequestBody 필드 검증에서 실패할 때 발생하는 예외이다.
→ 결과적으로 ApiResponse.onFailure("_BAD_REQUEST", errors, null)같은 형식으로 통일된 에러 응답 포맷을 생성

ResponseEntityExceptionHandler 메서드

image

ResponseEntityExceptionHandler는 스프링이 제공하는 예외 처리용 추상 클래스이다. 사진 말고도 엄청 많은 메서드가 이씀..
@ControllerAdvice에서 상속하면 스프링 MVC가 발생시키는 표준 예외들을 미리 정의된 메서드로 잡아서 처리할 수 있게한다.

  • handleHttpRequestMethodNotSupported : 지원하지 않는 HTTP 메서드(ex) put 요청 불가)
  • handleHttpMediaTypeNotSupported : 지원하지 않는 Content-Type
  • handleMissingServletRequestParameter : 필수 요청 파라미터 누락
  • handleTypeMismatch : 파라미터 타입 불일치
  • ⭐ handleMethodArgumentNotValid : @Valid로 DTO 검증 실패 시 발생
  • handleBindException : GET 파라미터 바인딩 실패
  • handleMissingPathVariable : PathVariable 누락
  • handleServletRequestBindingException : 헤더, 쿠키 바인딩 실패
  • handleExceptionInternal : ResponseEntity 만들 때 내부적으로 사용됨

exception()

위의 핸들러들에서 잡지 못하는 모든 에러들을 처리하는 핸들러 이다.
NullPointerException, ArrayIndexOutOfBoundsException 등 개발자가 예측하지 못한 런타임 에러들..
서버에러 _INTERNAL_SERVER_ERROR (HTTP 500) 상태를 기반으로 표준 응답(ResponseEntity)을 생성하여 반환한다. ⇒ handleExceptionInternalFalse 호출

onThrowException

이 메서드는 개발자가 직접 던진 커스텀 예외를 처리하는 코드이다.
비지니스 로직에서 발생하는 예외를 잡아서 표준 응답 형식으로 바꿔주는 역할 !
예외 발생시 ErrorReasonDTO → ApiResponse → ResponseEntity 로 변환 ⇒ handleExceptionInternal 호출

handleExceptionInternal

ExceptionAdvice 안에서 반복적으로 처리되는 “예외 → ApiResponse → ResponseEntity 변환” 과정을 한 곳에 모아 재사용하도록 하는 헬퍼 메소드 이다.

data 필드는 null로 설정

👉 핸들러(onThrowException)는 언제!! 예외를 처리할지 정함

👉 헬퍼(handleExceptionInternal)는 어떻게!! 응답을 만들지 정함

handleExceptionInternalFalse

exception() 메서드에서 호출하는 메서드로 일반적인 예외가 발생했을때 일관된 형태(ApiResponse)로 감싸서 ResponseEntity로 만들어주는 헬퍼 메서드이다.
여기서는 data 필드에 어디서 에러 메세지를 담는 errorpoint 를 담눈다.

handleExceptionInternalArgs

MethodArgumentNotValidException (DTO 유효성 검사) 처리를 위한 표준 응답 ResponseEntity를 생성하는 헬퍼메소드 이다.

handleExceptionInternalConstraint

ConstraintViolationException (파라미터 유효성 검사) 처리를 위한 표준 응답 ResponseEntity를 생성하는 헬퍼메소드이다.

예외 처리 로직 image
  • ApiResponse : 응답 형식 통일 (성공, 실패에 대한 응답 포맷 정의)
  • GeneralException :모든 비지니스 예외의 부모 클래스
  • ErrorHandler : 개발자가 직접 던지는 커스텀 예외 클래스
    • GeneralException을 상속받아 errorstatus 만 넣으면 바로 사용할 수 있도록
  • ExceptionAdvice : 전역 예외 처리기 - 모든 예외를 잡아서 ApiResponse로 변환

🚨 예외 발생 ->

  1. throw new ErrorHandler(ErrorStatus.___)

  2. GeneralException을 상속받은 ErrorHandler 생성

  3. ExceptionAdvice (@RestControllerAdvice)에서 잡힘

  4. handleExceptionInternal( ) 호출

  5. ApiResponse.onFailure( ) 로 통일된 실패 응답 생성

  6. ResponseEntity(ApiResponse) 로 클라이언트에게 JSON 반환

WebRequest

ExceptionAdvice 코드에서 WebRequest가 계속 보이길래 HttpServletRequest을 안쓰고 왜 이걸 쓰는지 궁금해서 찾아봤다. WebRequst는 SpringMVC에서 HTTP 요청 정보를 추상화한 인터페이스이다.

HttpServletRequest 는 java servlet 에서 제공하는 인터페이스이고, 이 WebRequest 은 Spring Framework에서 제공하는 인터페이스이다. HttpServletRequest 보다 높은 수준의 추상화를 제공하고, spring mvc를 사용할때 spring의 다양한 기능과 통합할 수 있는 장점이 있다고 한다.

컨트롤러단에서는 HttpServletRequest 를 자주 사용하고, 예외 처리나 핸들러에서는 WebRequest 를 사용하는 것이 더 일반적이고 범용성이 높다고 함.>!>>!

비교 항목 HttpServletRequest WebRequest
속한 패키지 javax.servlet.http (서블릿 전용) org.springframework.web.context.request (Spring 공용)
의존성 서블릿 환경 필수 서블릿/Reactive 모두 지원
용도 요청 헤더, 바디, 세션, URI 등 세밀 제어 스프링 내 요청 컨텍스트 접근용
주 사용처 컨트롤러, 필터, 서블릿 예외 처리, 인터셉터, 스프링 내부 컴포넌트
@RestControllerAdvice

@RestControllerAdvice는 프로젝트 전체의 Controller에서 발생하는 예외(Exception) 를 한 곳에서 통합적으로 처리하기 위한 Spring의 전역 예외 처리 기능

image

여기서 볼 수 있듯이 RestControllerAdvice는 ControllerAdvice와 ResponseBody를 합친 것이다. 전역 예외를 처리하되, 그 결과를 json으로 변환할 수 있도록 한다.

  1. 컨트롤러나 서비스 계층에서 예외 발생
  2. 스프링이 컨트롤러로 예외를 전달하려 함
  3. @RestControllerAdvice가 등록되어 있다면, 스프링이 이 예외를 대신 처리할 수 있는 핸들러가 있는지 찾음
  4. 해당 예외를 처리하는 @ExceptionHandler 메서드 실행
  5. 결과를 ResponseEntity 형태(JSON 응답)로 클라이언트에게 반환

장점

  1. 전역 예외 처리 - 한곳에서 모든 예외를 처리 ⇒ 코드 중복 제거

    모든 컨트롤러에서 발생한 예외를 하나의 클래스에서 일괄처리할 수 있따. 이로서, 컨트롤러마다 try-catch문을 반복해서 작성할 필요가 없고, 예외나 로깅/응답 형식 통일이 가능하다. 따라서 유지보수가 쉽다는 장점이 있다. 모든 컨트롤러마다 예외 처리 코드를 적어야한다면......끔찍....

  2. 응답 형식 통일 - ApiResponse 구조로 일관된 에러 응답 제공

    모든 오류응답이 같은 json 형태로 제공된다. 이건 프론트엔드와의 협업에서도 굉장히 중요한 부분이기에 꼭 필요하다. 만약 응답 형식이 통일되지 않으면 컨트롤러마다 구조가 달라져버리기때문에 api 명세 유지가 어렵고, 프론트엔드에서 일관된 처리가 불가능하다.

  3. 예외 종류별 세밀한 분기 처리 가능

    • GeneralException → 비즈니스 예외
    • ConstraintViolationException → 요청 파라미터 유효성 실패
    • MethodArgumentNotValidException@Valid DTO 검증 실패
    • Exception → 그 외 모든 예외
시니어 미션 - 500 에러 발생 시 알림 전송 기능 구현 (디스코드) image

일단 결과 사진만 첨부할게요...

## 📌 PR 포인트 -

✅ 체크리스트

  • Reviewer에 파트장을 선택 했나요?
  • Assignees에 본인을 선택 했나요?
  • Merge 하려는 브랜치가 올바르게 설정되어 있나요?
  • 컨벤션을 지키고 있나요?
  • 로컬에서 실행했을 때 에러가 발생하지 않나요?
  • 불필요한 주석이 제거되었나요?
  • 코드 스타일이 일관적인가요?

🤔 질문 & 고민

@Yujin1219 Yujin1219 self-assigned this Nov 3, 2025
@Yujin1219 Yujin1219 added ✨ Feature 기능 개발 ✅ Test test code labels Nov 3, 2025
@Yujin1219 Yujin1219 linked an issue Nov 3, 2025 that may be closed by this pull request
3 tasks
@Yujin1219 Yujin1219 merged commit f4abb0d into main Nov 3, 2025
1 check passed
@Yujin1219 Yujin1219 deleted the Week7 branch November 3, 2025 07:09
Copy link

@ggamnunq ggamnunq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발 ✅ Test test code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[✨ Feat] 7주차-API 응답 통일 & 에러 핸들러

3 participants