SEB_BE_43 / 23.02.16 회고

rse·2023년 2월 16일
0

코드스테이츠_BE_43

목록 보기
36/65

오늘

  • 예외 처리

DTO 유효성 검사시 저번처럼 콘솔에 입력되는 딱딱한 에러보다 조금 더 친절한 메세지를 에러로 뜨게 해보자.

@ExceptionHandler

Error Response 클래스를 만들어서 클라이언트 쪽에 전달해주자.

Response Body를 보면 응답이 배열인 것을 알 수 있다.
배열인 이유는 DTO 클래스에서 검증해야하는 변수가 하나 이상일 수 있기 때문에 에러도 하나 이상 될 수 있다는 뜻이다.

그래서 ErrorResponse 클래스에서 리스트로 정의를 해줬다. 이 한개의 필드 에러 정보는 FieldError 라는 별도의 static class를 ErrorResponse 클래스의 멤버 클래스로 정의해줬다.

아까는 통째로 전달했지만 이번에는 필요한 정보만 골라서 전송하고 있다.

에러를 보면 이메일 부분만 나오게.

이름 에러는 이름만 나오게 응답이 되고 있다.

문제점

  • 각각의 Controller 클래스에서 @ExceptionHandler를 사용하여 Request Body에 대한 유효성 검증 실패 에러 처리를 해야하므로 코드 중복 발생.

  • 또, 예외가 유효성 예외만 있는 것이 아니므로 하나의 Controller 클래스 내부에서 @ExceptionHandler 에러 핸들러 메서드가 늘어난다.

@RestControllerAdvice

특정 클래스에 @RestControllerAdvice 애너테이션을 추가하면 여러개의 Controller 클래스에 추가된 @ExceptionHandler, @InitBinder 또는 @ModelAttribute 메서드를 공유해서 사용 할 수 있다.

즉, 예외 처리를 공통화 할 수 있다는 것이다.

일단 MemberController 에 있는 @ExceptionHandler 이 붙어있던 메서드들을 지워준다.

@Getter
public class ErrorResponse {
    private List<FieldError> fieldErrors; // DTO 변수 필드의 유효성 검증 실패로 발생한 에러
    private List<ConstraintViolationError> violationErrors;  // URL 변수 값을 유효성 검증에 실패로 발생한 에러
    private ErrorResponse(List<FieldError> fieldErrors, List<ConstraintViolationError> violationErrors) {
        this.fieldErrors = fieldErrors;
        this.violationErrors = violationErrors;
    }
    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));
    }
    @Getter
    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(),
                            error.getDefaultMessage()))
                    .collect(Collectors.toList());
        }
    }
    @Getter
    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());
        }
    }
}

ErrorResponse 클래스을 private 으로 지정함으로 new ErrorResponse 방식으로 객체를 생성할 수 없음.

대신 of() 메서드를 이용해서 ErrorResponse 객체를 생성할 수 있다.

MethodArgumentNotValidException 에 ErrorResponse 객체를 생성해줌.

of() 메서드

흔히 볼 수 있는 네이밍 컨벤션.
보통 객체 생성시 어떤 값들의(of~) 객체를 생성한다는 의미에서 of() 메서드를 사용한다.

그리고 이제 GlobalExceptionAdvice 클래스를 수정.

@RestControllerAdvice 가 Controller 클래스에 @RestController 를 찾아서 자동으로 예외처리를 해준다.

profile
기록을 합시다

0개의 댓글