Just Do IT!

@RestControllerAdivce 적용해서 Spring 전역 예외처리하기 본문

개발 공부/Spring

@RestControllerAdivce 적용해서 Spring 전역 예외처리하기

MOON달 2024. 10. 21. 12:22
728x90
반응형

ControllerAdvice

  • 전역적으로 ExceptionHandler를 적용할 수 있는 @ControllerAdvice와 @RestControllerAdvice 어노테이션을 제공하고 있다.
  • @ControllerAdvice는 여러 컨트롤러에 전역적으로 ExceptionHandler를 적용해준다
  • 만약 특정 클래스에만 제한적으로 적용하고 싶다면 @RestControllerAdvice의 basePackages 등을 설정함으로 제한할 수 있다.
  • @RestControllerAdvice는 @ControllerAdvice와 다리 @ResponseBody가 붙어 있어 응답을 JSON으로 내려준다

 

@RestControllerAdvice 와 @ControllerAdvice 의 특징과 차이

  • @RestControllerAdvice  @ControllerAdvice + @ResponseBody 의 조합으로, RESTful API를 개발할 때 사용한다.
    • 응답을 JSON 형식으로 내려줍니다.
  • @ControllerAdvice 는 MVC 패턴을 사용할 때 적합하며, 응답에 @ResponseBody 를 추가해야 한다.
  • @RestControllerAdvice  @ControllerAdvice 는 패키지 단위로 적용할 수 있다.
  • 예를 들어, @ControllerAdvice ("com.controller")  @RestControllerAdvice ("com.rest.controller") 로 각각의 컨트롤러에 맞는 예외 처리를 할 수 있다.

 

@RestControllerAdvice, @ControllerAdvice의 장점

  • 하나의 클래스로 모든 컨트롤러에 대해 전역적으로 예외 처리가 가능하다.
  • 직접 정의한 에러 응답을 일관성 있게 클라이언트에게 내려줄 수 있다.
    • 여러 컨트롤러에서 발생하는 동일한 예외에 대해 한 곳에서 처리할 수 있다.
  • 별도의 try-catch문이 없어 코드의 가독성이 높아진다.
  • 예외에 따라 다른 처리 로직을 적용할 수 있다.
    • @ExceptionHandler 어노테이션을 사용하여 특정 에러 상황에 따다른 핸들러 메소드를 정의하여 사용할 수 있다.

 

 

 

 

 

 

@RestController 적용 예시

위의 장점들을 통해 예외 처리를 common 패키지에 추가하여 전역적으로 처리하기로 했다.

지금 진행하는 프로젝트에 적용했던 예외 처리 예시이다.

 

 

ErrorController 

@Slf4j
@RestControllerAdvice(annotations = RestController.class)
public class ErrorController {

    @ExceptionHandler(BaseException.class)
    protected ResponseEntity<ErrorResponse> handleBaseException(BaseException e) {
        return ResponseEntity
            .status(e.getHttpStatus())
            .body(ErrorResponse.from(e.getErrorCode()));
    }

    // @valid 에서 binding error 발생
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        List<String> params = new ArrayList<>();

        for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
            params.add(fieldError.getField() + ":" + fieldError.getDefaultMessage());
        }

        String errorMessage = String.join(", ", params);

        ErrorResponse response = ErrorResponse.from(ErrorCode.VALIDATION_FAILED);
        response.changeMessage(errorMessage);

        return response;
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(RuntimeException.class)
    protected ErrorResponse handleRuntimeException(RuntimeException e) {
        log.error(e.getMessage());

        return ErrorResponse.from(ErrorCode.INTERNAL_SERVER_ERROR);
    }
}

 

 

  • @RestControllerAdvice(annotations = RestController.class)
    • 전역 예외 처리하는 controller임을 나타낸다.
    • @RestController 어노테이션이 붙은 모든 컨트롤러에서 발생하는 예외를 처리한다.
  • handleBaseException
    • BaseException 타입의 예외를 처리한다.
    • BaseException은 사용자가 직접 정의한 예외이다.
    • 예외에서 HTTP 상태 코드와 에러 코드를 가져와서 ErroResponse 객체를 생성한 후, 해당 객체를 클라이언트에 반환한다.
  • handleMethodArgumentNotValidException
    • @Valid로 검증 시 발생하는 바인딩 오류를 처리한다.
    • 오류 필드와 메세지를 수집하여 params 리스트에 추가한다.
    • 최종적으로 오류 메세지를 하나의 문자열로 결합하고 ErrorResponse 객체를 생성하여 클라이언트에 반환한다.
  • handleRuntimeException
    • 일반적인 런타임 에외를 처리한다.
    • 예외 메세지를 로그로 기록하고 내부 서버 오류를 나타내는 INTERNAL_SERVER_ERROR를 반환한다.
  • 각 메서드는 예외에 대한 적절한 HTTP 상태 코드와 함께 ErrorResponse 객체를 반환하면서 API 응답의 일관성을 유지한다.

 

ErrorResponse

@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ErrorResponse {

    @JsonProperty("errorCode")
    private String code;

    @JsonProperty("errorMessage")
    private String message;

    public static ErrorResponse from(ErrorCode errorCode) {
        return ErrorResponse
            .builder()
            .code(errorCode.getCode())
            .message(errorCode.getMessage())
            .build();
    }

    public void changeMessage(String message) {
        this.message = message;
    }

}

 

  • @AllArgsConstructor(access = AccessLevel.PRIVATE)
    • 모든 필드를 매개변수로 받는 생성자를 생성
    • 접근 수준을 PRIVATE로 설정하여 외부에서 직접 인스턴스를 생성할 수 없게 한다.
  • @NoArgsConstructor(access = AccessLevel.PROTECTED)
    • 매개변수가 없는 생성자를 생성=
    • 이 생성자는 PROTECTED로 설정되어 있어 서브클래스에서만 사용할 수 있다.
  • @JsonProperty("errorCode")
    • JSON 직렬화 및 역직렬화 시 errorCode라는 이름으로 변환
    • 이 필드는 오류 코드 문자열을 저장
  • @JsonProperty("errorMessage")
    • 마찬가지로 JSON 변환 시 errorMessage라는 이름을 사용
    • 이 필드는 오류 메시지를 저장
  • from(ErrorCode errorCode)
    • 주어진 ErrorCode를 기반으로 ErrorResponse 객체를 생성한다.
  • changeMessage
    • 오류 메세지를 변경하는 메서드이다.
  • API에서 발생하는 오류에 대한 구조화된 응답을 정의한다.

 

BaseException

@Getter
@RequiredArgsConstructor
public class BaseException extends RuntimeException {

    public static final BaseException VALIDATION_FAILED = new BaseException(ErrorCode.VALIDATION_FAILED);
    public static final BaseException BOOK_CATEGORY_ERROR = new BaseException(ErrorCode.BOOK_CATEGORY_ERROR);
    public static final BaseException BOOK_CATEGORY_NOT_FOUND = new BaseException(ErrorCode.BOOK_CATEGORY_NOT_FOUND);
    public static final BaseException BOOK_SEARCH_NOT_FOUND = new BaseException(ErrorCode.BOOK_SEARCH_NOT_FOUND);
    public static final BaseException INTERNAL_SERVER_ERROR = new BaseException(ErrorCode.INTERNAL_SERVER_ERROR);

    private final ErrorCode errorCode;

    @Override
    public synchronized Throwable fillInStackTrace() {
        return this; // 스택 트레이스 생략
    }

    public HttpStatus getHttpStatus() {
        return errorCode.getHttpStatus();
    }
}
  • RuntimeException을 확장하여 사용자 정의 예외를 정의하는 클래스이다
  • 정적 예외 인스턴스를 정의해서 각기 다른 오류 상황을 표현한다.
  • fillInstaceTrace
    • 스택 트레이스를 생략하여 성능 개선
    • 예외의 발생 위치를 추적할 필요가 없는 경우에 유용하다.
  • getHttpStatus
    • 관련된 ErrorCode에서 HTTP 상태 코드를 반환하는 메서드

 

ErrorCode (enum)

@Getter
@RequiredArgsConstructor
public enum ErrorCode {

    /* 400 */
    VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "VALIDATION_FAILED", "입력값 유효성 검사에 실패했습니다."),

    /* 404 */
    BOOK_CATEGORY_ERROR(HttpStatus.NOT_FOUND, "BOOK_CATEGORY_ERROR", "해당 카테고리는 존재하지 않습니다"),
    BOOK_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "BOOK_CATEGORY_NOT_FOUND", "해당 카테고리에 포함된 문제집이 없습니다."),
    BOOK_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, "BOOK_SEARCH_NOT_FOUND", "검색어와 일치하는 문제집이 없습니다."),

    /* 500 */
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR", "예상치 못한 서버 에러가 발생했습니다.");

    private final HttpStatus httpStatus;
    private final String code;
    private final String message;
}
  • 각 오류에 대한 세부 정보를 정의하는 enum
  • HTTP 상태 코드, 코드 문자열, 사용자에게 보여줄 메세지를 포함한다.

 

예외 처리 적용 예시

    @Override
    public List<BookResponse> searchBooksByKeyword(String keyword) {
        List<Book> bookList = bookRepository.findAllByTitleContainingAndSecretFalse(keyword);
        
        // 해당 검색어와 일치하는 문제집이 없는 경우 예외처리
        if(bookList.isEmpty()) {
            log.warn("No books found for keyword: {}", keyword);
            throw new BaseException(ErrorCode.BOOK_SEARCH_NOT_FOUND);
        }
        
        return convertToBookResponseList(bookList);
    }

 

검색어와 일치하는 문제집이 없는 경우 error message가 나오도록 예외처리를 해주었다.

 

postman 확인 결과

 

이렇게 지정했던 404 Not Found 오류와 함께 에러 메세지가 함께 나오는 걸 확인할 수 있다.

 

 

 

 


프로젝트를 진행하다 보니 예외 처리에 대한 고민이 생겼다.

깃허브의 다른 프로젝트를 구경하다보니 전역적으로 예외 처리를 구현해놓은 프로젝트가 있어서

그걸 참고로 우리 프로젝트에 맞게 예외 처리를 추가해주었다.

 

정말...이번 프로젝트에서 새로 해보는 게 많은 것 같아서 즐겁다.

 

 

참고 링크:

https://velog.io/@u-nij/Spring-%EC%A0%84%EC%97%AD-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-RestControllerAdivce-%EC%A0%81%EC%9A%A9

 

Spring 전역 예외 처리: @RestControllerAdivce 적용

Spring Boot에서 @RestControllerAdivce 어노테이션을 사용한 전역 예외 처리 방법에 대해 정리한 글입니다.

velog.io

https://github.com/DevTraces/BackEnd

 

GitHub - DevTraces/BackEnd: 🖼 일상의 예술을 공유하는 SNS

🖼 일상의 예술을 공유하는 SNS. Contribute to DevTraces/BackEnd development by creating an account on GitHub.

github.com

https://velog.io/@yoonuk/Spring-%EC%A0%84%EC%97%AD-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC

 

[Spring] 전역 예외 처리

Spring에서는 @ControllerAdvice 어노테이션과 @RestControllerAdvice 어노테이션을 사용하여 컨트롤러에서 발생하는 예외를 전역적으로 처리할 수 있습니다. Spring에서 예외 처리를 할 때, @RestControllerAdvice

velog.io

 

728x90