[Spring boot] RestControllerAdvice를 이용한 예외 처리

2022. 6. 13. 06:47· Spring boot
목차
  1. 유효성 검사
  2. @RestControllerAdvice 를 이용해서 예외 처리를 해보자!
  3. 1. 에러코드 정의하기
  4. 2. 에러 응답 클래스 정의하기
  5. 3. @RestControllerAdvice 구현하기

들어가며

@Valid 어노테이션으로 유효성 검사를 해주고 예외 처리를 해주지 않으면 사진과 같이 에러 로그가 그대로 노출되어버린다.

 

 

이렇게 되면 클라이언트 입장에서 유용한 정보를 주기도 어렵고, trace에서 운영환경에서의 구현이 노출되기 때문에 해커의 위협에서 벗어나기 어렵다. 

 

따라서 적절하게 예외 처리를 함으로써 에러 응답을 변경해 줄 필요가 있고, 이 예외 처리 방법 중 가장 좋은 방식이 @RestControllerAdivce (혹은 @ControllerAdvice)어노테이션과 @ExceptionHandler를 함께 사용하는 방식이라고 한다.

 

다양한 예외처리 방법과 @RestControllerAdivce 어노테이션을 사용하는게 가장 좋은 이유는 다음 글을 참고하자.

 

 

[Spring] Spring의 다양한 예외 처리 방법(ExceptionHandler, ControllerAdvice 등) 완벽하게 이해하기 - (1/2)

 

[Spring] Spring의 다양한 예외 처리 방법(ExceptionHandler, ControllerAdvice 등) 완벽하게 이해하기 - (1/2)

예외 처리는 robust한 애플리케이션을 만드는데 매우 중요한 부분을 차지한다. Spring 프레임워크는 매우 다양한 에러 처리 방법을 제공하는데, 어떠한 방법들이 있고 가장 좋은 방법(Best Practice)은

mangkyu.tistory.com

 

 


유효성 검사

유효성 검사는 앞서 말했듯이 @Valid 어노테이션을 활용해서 적용해주었다.

 

@Valid 어노테이션의 사용 방법은 아래 포스팅을 참고하자.

 

[TIL] 06/08 항해99 27일차 - JPA 예외처리, 삽질로그

 

[TIL] 06/08 항해99 27일차 - JPA 예외처리, 삽질로그

들어가며 다른 수강생분 덕에 계속 머리싸매던 부분을 속시원하게 해결했다! 적용해보고 싶은게 많았지만 전부다 해보기는 수정해야할 부분이 너무 많아서 하나씩 적용할 수 있는 부분들만 하

annajin.tistory.com

 

 

@RestControllerAdvice 를 이용해서 예외 처리를 해보자!

@RestControllerAdvice를 적용하기 위해 두 가지의 시도를 해보았고, 내가 보기에 좋아보이는 방식을 적용해주었다.

 

 

최종적으로 완성된 패키지 구조는 아래와 같다. 사진에서 볼 수 있는 UserErrorCode는 아직 사용되는 부분이 없지만 미리 구현해둔 클래스이기 때문에 따로 언급하지 않겠다.

 

 

1. 에러코드 정의하기

클라이언트에게 보내줄 에러 코드를 정의해주기 위해서 클래스를 추가해준다.

에러 이름, HTTP 상태, 에러 메세지를 가지고 있는 클래스를 만들어주는 단계이다. 

 

에러 코드를 클래스별로 나누기 위해 ErrorCode를 인터페이스로 구현 해준 다음, CommonErrorCode에 추상화해주었다.

 

 

ErrorCode.java

public interface ErrorCode {
    String name();
    HttpStatus getHttpStatus();
    String getMessage();
}

 

CommonErrorCode.java

@Getter
@RequiredArgsConstructor
public enum CommonErrorCode implements ErrorCode {

    INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "Invalid parameter included"),
    RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "Resource not exists"),
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error"),
    ;

    private final HttpStatus httpStatus;
    private final String message;

}

 

CommonErrorCode에는 전역적으로 나타나는 에러 코드들과 메세지가 정의되어 있다. 

 

 

그리고 발생한 예외를 처리해줄 예외 클래스(Exception Class)를 추가해준다.

 

 

RestApiException.java

@Getter
@RequiredArgsConstructor
public class RestApiException extends RuntimeException{

    private final ErrorCode errorCode;

}

 

 

2. 에러 응답 클래스 정의하기

아래와 같은 형태의 응답을 보내주기 위해 에러 응답 클래스를 추가해준다.

 

{
    "code": "INACTIVATE_USER",
    "message": "User is inactive"
}

 

 

ErrorResponse.java

@Getter
@Builder
@RequiredArgsConstructor
public class ErrorResponse {

    private final String code;
    private final String message;

    // Errors가 없다면 응답이 내려가지 않게 처리
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private final List<ValidationError> errors;

    @Getter
    @Builder
    @RequiredArgsConstructor
    public static class ValidationError {
        // @Valid 로 에러가 들어왔을 때, 어느 필드에서 에러가 발생했는 지에 대한 응답 처리
        private final String field;
        private final String message;

        public static ValidationError of(final FieldError fieldError) {
            return ValidationError.builder()
                    .field(fieldError.getField())
                    .message(fieldError.getDefaultMessage())
                    .build();
        }
    }
}

 

 

 

3. @RestControllerAdvice 구현하기

이제 마지막이다. 전역적으로 에러를 처리해주는 @RestControllerAdvice를 구현해주면 된다.

 

Spring은  예외를 미리 처리해둔 ResponseEntityExceptionHandler를 추상클래스로 제공하고 있기 때문에 이를 상속받아주면 되는데, 에러 메세지는 반환하지 않기 때문에 handleExceptionInternal을 오버라이드 해줘야한다.

 

 

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    // RuntimeException 처리
    @ExceptionHandler(RestApiException.class)
    public ResponseEntity<Object> handleCustomException(RestApiException e) {
        return handleExceptionInternal(e.getErrorCode());
    }

    // IllegalArgumentException 에러 처리
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Object> handleIllegalArgument(IllegalArgumentException e) {
        log.warn("handleIllegalArgument", e);
        return handleExceptionInternal(CommonErrorCode.INVALID_PARAMETER, e.getMessage());
    }

    // @Valid 어노테이션으로 넘어오는 에러 처리
    @Override
    public ResponseEntity<Object> handleBindException(
            BindException e,
            HttpHeaders headers,
            HttpStatus status,
            WebRequest request) {
        log.warn("handleIllegalArgument", e);
        return handleExceptionInternal(e, CommonErrorCode.INVALID_PARAMETER);
    }

    // 대부분의 에러 처리
    @ExceptionHandler({Exception.class})
    public ResponseEntity<Object> handleAllException(Exception ex) {
        log.warn("handleAllException", ex);
        return handleExceptionInternal(CommonErrorCode.INTERNAL_SERVER_ERROR);
    }
    
    // RuntimeException과 대부분의 에러 처리 메세지를 보내기 위한 메소드
    private ResponseEntity<Object> handleExceptionInternal(ErrorCode errorCode) {
        return ResponseEntity.status(errorCode.getHttpStatus())
                .body(makeErrorResponse(errorCode));
    }
    
    // 코드 가독성을 위해 에러 처리 메세지를 만드는 메소드 분리
    private ErrorResponse makeErrorResponse(ErrorCode errorCode) {
        return ErrorResponse.builder()
                .code(errorCode.name())
                .message(errorCode.getMessage())
                .build();
    }

    private ResponseEntity<Object> handleExceptionInternal(ErrorCode errorCode, String message) {
        return ResponseEntity.status(errorCode.getHttpStatus())
                .body(makeErrorResponse(errorCode, message));
    }

    // 코드 가독성을 위해 에러 처리 메세지를 만드는 메소드 분리
    private ErrorResponse makeErrorResponse(ErrorCode errorCode, String message) {
        return ErrorResponse.builder()
                .code(errorCode.name())
                .message(message)
                .build();
    }

    // @Valid 어노테이션으로 넘어오는 에러 처리 메세지를 보내기 위한 메소드
    private ResponseEntity<Object> handleExceptionInternal(BindException e, ErrorCode errorCode) {
        return ResponseEntity.status(errorCode.getHttpStatus())
                .body(makeErrorResponse(e, errorCode));
    }

    // 코드 가독성을 위해 에러 처리 메세지를 만드는 메소드 분리
    private ErrorResponse makeErrorResponse(BindException e, ErrorCode errorCode) {
        List<ErrorResponse.ValidationError> validationErrorList = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(ErrorResponse.ValidationError::of)
                .collect(Collectors.toList());

        return ErrorResponse.builder()
                .code(errorCode.name())
                .message(errorCode.getMessage())
                .errors(validationErrorList)
                .build();
    }
}

 

 

유효성 검사를 통과하지 못할 때 에러처리 응답이 어떻게 넘어오는지 확인해보면 이쁘게 잘 넘어오는 걸 볼 수 있다.

 

 


마치며

코드의 대부분은 링크된 글을 참고했다!

 

 


출처 및 참고

 

  • 본문 예외처리 코드

[Spring] @RestControllerAdvice를 이용한 Spring 예외 처리 방법 - (2/2)

 

  • @Valid만 예외처리된 코드

 

@Valid 와 @ControllerAdvice로 DTO 예외처리하기

 

 

저작자표시 비영리 (새창열림)

'Spring boot' 카테고리의 다른 글

[JPA] 영속성 관리  (0) 2022.06.21
[JPA] JPA, Hibernate, Spring Data JPA의 차이점  (0) 2022.06.20
[Spring boot] 스프링 프레임워크(Spring Framework)  (0) 2022.06.09
[JPA] ORM(Object-Relational Mapper)  (0) 2022.06.05
[Spring boot] Ajax로 보낸 JSON 서버에서 받기  (0) 2022.04.12
  1. 유효성 검사
  2. @RestControllerAdvice 를 이용해서 예외 처리를 해보자!
  3. 1. 에러코드 정의하기
  4. 2. 에러 응답 클래스 정의하기
  5. 3. @RestControllerAdvice 구현하기
'Spring boot' 카테고리의 다른 글
  • [JPA] 영속성 관리
  • [JPA] JPA, Hibernate, Spring Data JPA의 차이점
  • [Spring boot] 스프링 프레임워크(Spring Framework)
  • [JPA] ORM(Object-Relational Mapper)
Anna-Jin
Anna-Jin
Anna-Jin
내일 한걸음 더
Anna-Jin
TOTAL
TODAY
YDAY
  • CATEGORY (212)
    • Project (0)
      • Zero2One.Dev (0)
    • Algorithm (40)
      • Leetcode (20)
      • Programmers (1)
      • CODETREE (0)
      • Baekjoon (7)
      • etc (12)
    • JAVA (42)
      • JAVA (20)
      • 점프투자바 (16)
      • 이것이 자바다 (6)
    • Spring boot (20)
    • Database (9)
    • CS (11)
    • Study (80)
      • Trouble Shooting (11)
      • TIL (50)
      • WIL (11)
      • Etc (8)
    • Review (10)
    • Projects (0)
      • Blog (0)

BLOG MENU

  • GITHUB
  • RESUME

공지사항

POPULAR POSTS

태그

  • 이것이 자바다
  • leetcode
  • 삽질로그
  • 자료구조
  • 회고록
  • spring boot
  • Algorithm
  • 자바
  • 항해99
  • MySQL
  • JPA
  • til
  • 코딩테스트
  • 코테
  • Wil
  • 알고리즘
  • 확인문제
  • Java
  • 리트코드
  • 트러블슈팅

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.1
Anna-Jin
[Spring boot] RestControllerAdvice를 이용한 예외 처리
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.