Spring/예외처리

[Spring] 예외처리 - Spring MVC

한비Skyla 2024. 6. 13. 21:36

bindingResult : 스프링이 제공하는 검증 오류 보관 객체. 검증 오류가 발생하면 BindingResult 객체에 보관. 

 - @ModelAttribute 바로 옆에 둬야 한다. 

ObjectError : 글로벌 오류. 

FieldError : 필드 오류. FieldError(검증 객체 이름, 오류가 발생한 필드 이름, 오류 기본 메시지)

 - 타입 오류로 바인딩에 실패하면 FieldError 를 생성하며 오류를 넣고, 해당 오류를 BindinResult 에 담아 컨트롤러를 호출. 

 

@RestControllerAdvice 

 - 여러 개의 Controller 클래스에서 @ExceptionHandler 가 추가된 메서드를 공유해 사용할 수 있다.

 - 이 에너테이션을 추가한 클래스를 통해 예외 처리를 공통화 할 수 있다. 

 

 

💡GlobalExceptionAdvice 

@RestControllerAdvice
//Controllerexception 에서 발생하는 거는 다 잡음.
public class GlobalExceptionAdvice {

    // 이 클래스 내에서 예외가 발생한 걸 찾음.
    @ExceptionHandler
    // exception 타입을 받아서 예외처리를 함.
    // exception 전체를 받아서 할 수도 있지만 ~
    // 사용자가 받는 것은 성공했는지 실패했는지를 판별할 때 알맞은 상태코드를 처리할 수 있어야 함.

    //MethodArgumentNotValidException 는 bindingResult를 상속받고 있음!!!!
    // 반드시 0보다 같거나 커야 합니다.
    public ResponseEntity handleException(MethodArgumentNotValidException e ) {
        // 예외가 발생하면 예외 클래스를 통해 발생하게 됨.
        // 필요없는 데이터를 없앨 것. 없애는 걸 ErrorResponse.of() 에서 할 것임.
        // MethodArgumentNotValidException 이 에러가 BindingResult 를 상속받고 있어서,
        // 타입을 이렇게 받아도 되는 건가??????
        BindingResult bindingResult = e.getBindingResult();
        // 이게 필요없는 데이터를 없앤 결과 response .
        ErrorResponse response = ErrorResponse.of(bindingResult);
        return new ResponseEntity(response, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler

    //RuntimeException을 상속 받고 있음.
       // JVM  정상적인 작동 중에 발생하는 클래스.
       // unchecked exception. 사용자가 예외 처리를 안해도 컴파일이 되기는 함. 코드가 실행이 되면 에러가 발생.
    // 파라미터나 리턴 값에 문제가 있을 때. 제약 조건이 위배되었을 때 발생하는 예외임.
    public ResponseEntity handleConstraintViolationException(ConstraintViolationException e) {

        // constrainViolations 는 Set<ConstraintViolation<?>>  타입.
        // s 인 이유가 있다고 했었는데...!!!!!!!!!
        Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
        ErrorResponse response = ErrorResponse.of(constraintViolations);
        return new ResponseEntity(response, HttpStatus.BAD_REQUEST);
    }

 

 

 

💡ErrorResponse 


// 내부 클래스를 쓰는 이유: 하나의 클래스 안에서만 쓸 때. 하나에다가만 몰아서 쓰는 것. 내부에서만 쓰기 위해서 담아놨다.
// 우리가 감춘건 생성자. 생성자는 객체를 만드는 기능을 감춘걸 매세드 오버로딩을 통해서 of 메서드를 구현.
@Getter
// static 이 붙어서 new 를 안 붙이고 만들 수 있다.
// 그래서 .of 만 붙이고 생성할 수 있다.
public class ErrorResponse {
    // dto 에서 발생하는 에러를 다 받음. 그래서 리스트로 받은 거임.
    private List<FieldError> fieldErrors;
    private List<ConstraintViolationError> violationErrors;

    // private 접근제어자를 받음. 동일 클래스 내에서만 받음. 다른 곳에서는 생성자를 호출할 수 없도록 함.
    // 내부에서 쓰겠다는 의미.
    private ErrorResponse(List<FieldError> fieldErrors, List<ConstraintViolationError> violationErrors) {
        this.fieldErrors = fieldErrors;
        this.violationErrors = violationErrors;
    }

    // (4) BindingResult에 대한 ErrorResponse 객체 생성
    // 동일 클래스에서 쓰고 있음, 생성자를 막아놓고, 불러와서 쓸 수 있음.
    public static ErrorResponse of(BindingResult bindingResult) {
                              // new를 통해 안 들고 쓸 수 있어야 해서 static 이 붙은 거임.
        return new ErrorResponse(FieldError.of(bindingResult), null);
                              // bindingResult 를 넣어서 호출하고 있음.
    }

    // (5) Set<ConstraintViolation<?>> 객체에 대한 ErrorResponse 객체 생성
    public static ErrorResponse of(Set<ConstraintViolation<?>> violations) {
        return new ErrorResponse(null, ConstraintViolationError.of(violations));
        // 외부에서 에러를 of 라는 하나의 기능으로 만들고 싶은 거임.
        // 발생하는 예외가 다를 때마다 다르게, 생성자를 만드는 것임.
    }

    //메서드 오버로딩. 파라미터의 타입이 다름.

    // (6) Field Error 가공
    @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;
        }
        // FieldError.of 는 밑의 내용을 받아서 실행하는 거?????
        public static List<FieldError> of(BindingResult bindingResult) {
            // class 랑 이름이 같으니까 절대 경로를 지어주는 것임.
            // 얘는 FieldError 임폴트 한 온전한 FieldError 인 것임.
            // error 가 여러개 일 수 있으니 List 로 받는다??????
            final List<org.springframework.validation.FieldError> fieldErrors =
                    bindingResult.getFieldErrors();
            return fieldErrors.stream()
                    // error 를 새로운 FieldError 에 넣겠다.
                    // 밑에는 생성자 FieldError !!!!!!
                    // 돌명서 우리가 만드는 생성자 안에 필요한 데이터만 뽑아서 넣는 것.
                            .map(error -> new FieldError(
                            error.getField(),
                            // 오류가 발생한 필드 이름.
                                    // 필드는 클래스에 포함된 변수. 객체의 속성을 정의하는 공간.
                                    // 클래스 변수. 인스턴스 변수.
                            // 오류가 발생한 필드 이름 ? 없으면 빈 문자열 : 있으면 문자열로 전환해서 불러와라.
                            error.getRejectedValue() == null ?
                                    "" : error.getRejectedValue().toString(),
                            error.getDefaultMessage()))
                            // 데이터를 가공한 뒤에 그 요소들을 수집해라.
                            // 스트림의 요소를 List, Set, Map. 등 다른 타입의 결과로 수집하고 싶을 때.
                            .collect(Collectors.toList());
        }
    }