@RestControllerAdvice

개발하는 구황작물·2023년 3월 28일
0

메인 프로젝트

목록 보기
10/10

@ControllerAdvice

컨트롤러에 대해 @ExceptionHandler, @InitBinder, @ModelAttribute가 적용된 메소드에 AOP를 적용되기 위해 고안되었다. 여러 컨트롤러에 전역적으로 ExceptionHandler를 적용해준다.

여기서 @ExceptionHandler는 AOP를 이용한 예외처리 방법으로 메서드에 선언하여 예외처리를 하려는 클래스를 지정하면 예외 발생시 정의된 로직에 의해 처리한다.

@RestControllerAdvice

@ControllerAdvice@ResponseBody를 합쳐놓은 어노테이션이다. @ControllerAdvice와 같은 역할을 하며 @ResponseBody를 통해 객체를 리턴할 수 있다.

@ControllerAdvice@RestControllerAdvice를 사용할 때 주의점은 한 프로젝트당 하나만 선언해야 한다는 것이다.

사용법

  1. 예외들을 enum으로 선언
public enum ExceptionCode {

    COMPANY_NOT_FOUND(404, "Company Not Found"),
    COMPANY_ID_NOT_MATCHED(404, "Company id not matched"),
    .
    .
    .
    INTERNAL_SERVER_ERROR(500, "Method Not Allowed");

    @Getter
    private final int status;

    @Getter
    private final String message;

    ExceptionCode(int status, String message) {
        this.status = status;
        this.message = message;
    }
}
  1. 선언한 예외들을 위한 CustomException을 생성해준다.
public class BusinessLogicException extends RuntimeException{

    @Getter
    private ExceptionCode exceptionCode;

    public BusinessLogicException(ExceptionCode exceptionCode) {
        super(exceptionCode.getMessage());
        this.exceptionCode = exceptionCode;
    }
}

이런 식으로 예외 사용이 가능하다.

  1. @RestControllerAdvice 사용한 GlobalExceptionAdvice 클래스 생성
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {

    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleMethodArgumentNotValidException(
            MethodArgumentNotValidException e) { //@valid 예외 처리
        return ErrorResponse.of(e.getBindingResult());
    }

    @ExceptionHandler //CustomException 처리
    public ResponseEntity handleBusinessLogicException(BusinessLogicException e) {
        final ErrorResponse response = ErrorResponse.of(e.getExceptionCode());

        return new ResponseEntity<>(response, HttpStatus.valueOf(e.getExceptionCode()
                .getStatus()));
    }

    @ExceptionHandler
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public ErrorResponse handleHttpRequestMethodNotSupportedException(
            HttpRequestMethodNotSupportedException e) {

        return ErrorResponse.of(HttpStatus.METHOD_NOT_ALLOWED);
    }

    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleHttpMessageNotReadableException(
            HttpMessageNotReadableException e) { //잘못된 형식으로 request 를 요청할 경우 예외 발생

        return ErrorResponse.of(400,
                "Required request body is missing");
    }

    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleMissingServletRequestParameterException(
            MissingServletRequestParameterException e) { //필수 파라미터 결여시

        return ErrorResponse.of(400,
                e.getMessage());
    }

    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleConstraintViolationException( //@Validated 에러
            ConstraintViolationException e) {
        return ErrorResponse.of(e.getConstraintViolations());
    }

    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e) {
        return ErrorResponse.of(400, e.getMessage());
    }

    @ExceptionHandler
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponse handleException(Exception e) {
        log.error("# handle Exception", e);

        return ErrorResponse.of(HttpStatus.INTERNAL_SERVER_ERROR);
    }

}
  1. ErrorResponse 생성
@Getter
public class ErrorResponse {
    private Integer status;
    private String message;
    private List<FieldError> fieldErrors;
    private List<ConstraintViolationError> violationErrors;

    public ErrorResponse(Integer status, String message) {
        this.status = status;
        this.message = message;
    }

    private ErrorResponse(final List<FieldError> fieldErrors,
                          final List<ConstraintViolationError> violationErrors) {
        this.fieldErrors = fieldErrors;
        this.violationErrors = violationErrors;
    }

    public static ErrorResponse of(ExceptionCode exceptionCode) {
        return new ErrorResponse(exceptionCode.getStatus(), exceptionCode.getMessage());
    }

    public static ErrorResponse of(BindingResult bindingResult) {
        return new ErrorResponse(FieldError.of(bindingResult), null);
    }

    public static ErrorResponse of(Set<ConstraintViolation<?>> violations) {
        return new ErrorResponse(null, ConstraintViolationError.of(violations));
    }

    public static ErrorResponse of(HttpStatus http) {
        return new ErrorResponse(http.value(), http.getReasonPhrase());
    }

    public static ErrorResponse of(Integer status, String message) {
        return new ErrorResponse(status, message);
    }

    @Getter //@Validated 에러 발생시
    public static class FieldError {
        private String field;
        private Object rejectedValue;
        private String reason;

        private FieldError(String field, Object rejectedValue, String reason) {
            this.field = field;
            this.rejectedValue = rejectedValue;
            this.reason = reason;
        }

        public static List<FieldError> of(BindingResult bindingResult) {
            final List<org.springframework.validation.FieldError> fieldErrors =
                    bindingResult.getFieldErrors();
            return fieldErrors.stream()
                    .map(error -> new FieldError(
                            error.getField(),
                            error.getRejectedValue() == null ?
                                    "" : error.getRejectedValue().toString(),
                            error.getDefaultMessage()))
                    .collect(Collectors.toList());
        }
    }

    @Getter // @Valid 에서 에러 발생시
    public static class ConstraintViolationError {
        private String propertyPath;
        private Object rejectedValue;
        private String reason;

        private ConstraintViolationError(String propertyPath, Object rejectedValue,
                                         String reason) {
            this.propertyPath = propertyPath;
            this.rejectedValue = rejectedValue;
            this.reason = reason;
        }

        public static List<ConstraintViolationError> of(
                Set<ConstraintViolation<?>> constraintViolations) {
            return constraintViolations.stream()
                    .map(constraintViolation -> new ConstraintViolationError(
                            constraintViolation.getPropertyPath().toString(),
                            constraintViolation.getInvalidValue().toString(),
                            constraintViolation.getMessage()
                    )).collect(Collectors.toList());
        }
    }
}
profile
어쩌다보니 개발하게 된 구황작물

0개의 댓글