Spring & Spring boot

[Spring Boot] 예외 처리 - 2. 유효성 검사 Exception 처리

jh4dev 2024. 8. 16. 12:23



앞서, @Valid 어노테이션과 @Validated 어노테이션을 활용하여 유효성 검사를 수행하는 내용에 대해 포스팅을 작성하였다.

해당 내용에 이어, 유효성 검사에 실패했을 경우 Exception 처리를 하는 방법에 대해 알아보자.

 

관련 포스팅

 

[Spring Boot] Validation - 1. @Valid 어노테이션 사용방법

1. 유효성 검사2. @Valid 어노테이션 테스트3. 유효성 검사 어노테이션 [유효성 검사]애플리케이션의 비즈니스 로직이 올바르게 동작하기 위해, 데이터를 사전 검증하는 유효성 검사 작업이 필요

jh4dev.tistory.com

 

 

[Spring Boot] Validation - 2. @Validated 어노테이션 사용방법

1. @Valid vs @Validated2. @Validated 검증 그룹 지정하기 이전 포스팅에서, JAVA 에서 지원하는 @Valid 어노테이션에 대해 알아보았으며, 이번 포스팅에서는 스프링에서 지원하는 @Validated 어노테이션 사용

jh4dev.tistory.com

 

[예외 처리]

@Valid 의 경우, 일반적으로 JSR-303/JSR-380 Bean Validation을 수행할 때 사용되며, 클래스 / 메서드 파라미터 / 또는 메서드 반환 값에 적용될 수 있으며, "javax.validation.ConstraintViolationException"이 발생한다.

 

@Validated 는, Spring의 유효성 검사 메커니즘을 사용하며, 그룹 기반의 유효성 검사를 지원하며, 주로 서비스 계층에서 메서드 유효성 검사를 수행할 때 사용된다. 또한, 유효성 검사가 실패하면 케이스에 따라 "org.springframework.validation.BindException" 또는 "javax.validation.ConstraintViolationException"이 발생한다.

 

그러나, @Valid 어노테이션과 @Validated 어노테이션 둘 다 컨트롤러에서 사용되는 경우, 유효성 검사에 실패하면 "org.springframework.web.bind.MethodArgumentNotValidException" 이 발생한다.

 

따라서, 컨트롤러 단계의 유효성 검사에서 발생하는 예외는, 별도의 Advice 클래스나 컨트롤러 내, 아래와 같은 Exception Handler 를 작성하여 처리할 수 있다.

<ExceptionHandler Sample>

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> validationExceptionHandler(MethodArgumentNotValidException e, HttpServletRequest request){

        HttpHeaders responseHeaders = new HttpHeaders();
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;

        log.error("Advice 내 validAnnoExceptionHandler() 호출, {}, {}", request.getRequestURI(), e.getMessage());

        String message = "Validation failed";

        //유효성 검사에서 노출되는 DefaultMessage 가져오기
        FieldError fieldError = e.getBindingResult().getFieldError();
        if(fieldError != null) {
            message = fieldError.getDefaultMessage();
        }

        Map<String, String> responseMap = new HashMap<>();
        responseMap.put("error_type", httpStatus.getReasonPhrase());
        responseMap.put("code", "400");
        responseMap.put("message", message);

        return ResponseEntity.status(httpStatus).body(responseMap);
    }

 

자세히 볼 부분은 11번 라인부터이다.

Exception 인스턴스의 getMessage() 를 확인해보면, 아래와 같이 긴 Exception 메시지가 생성되어 있다.

 

Validation failed for argument [0] in public org.springframework.http.ResponseEntity<com.springboot.valid.data.dto.ValidatedRequestDto> com.springboot.valid.controller.ValidatedController.checkValidationByValidatedAnnoGroup1(com.springboot.valid.data.dto.ValidatedRequestDto): [Field error in object 'validatedRequestDto' on field 'phoneNumber': rejected value [string]; codes [Telephone.validatedRequestDto.phoneNumber,Telephone.phoneNumber,Telephone.java.lang.String,Telephone]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validatedRequestDto.phoneNumber,phoneNumber]; arguments []; default message [phoneNumber]]; default message [전화번호 형식이 올바르지 않습니다.]] 

 

[전화번호 형식이 올바르지 않습니다.] 라는 메시지가 클라이언트로 전달되면 좋을 것이다.

따라서, 해당 default message 값을 추출하는 부분이 11번 라인 ~ 15번 라인 내용이다.

 

스웨거를 통해 테스트해보면, 일목요연하게 필요한 메시지만 Response 로 리턴되는 것을 확인할 수 있다.

default message 처리 전

 

default message 처리 후