ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2. 스프링 Validation with custom message and custom response container
    스프링개발자/203 - Validation 2020. 8. 10. 03:31

    [배경]

    스프링 Validation에서 좀 더 많은 정보를 제공 할 수 없을까?

    다음은 제공해야 되는 field인 "rides"를 입력하지 않아서 생기는 400 에러이다

    postman response를 보면 timestamp, status, error, message, path의 정보가 있다.

    message에는 아무것도 표시 되지 않았다. 어떤 값이 잘못되어서 해당 오류가 난지 정보가 부족하다.


    1. NotNull 어노테이션에 정보 제공하기

    다시 postman request를 send해보면 여전히 "message": "" 로 메세지 표시되지 않았다.

    내가 입력한 메세지는, IDE에 run 텝에서 WARN종류의 log에만 기록되었다

    2. NotValidException을 인터셉트 하자

    어떻게 하면 response에서 보여지는 message를 override할 수 있을까?

    스프링 validation에서 제공하는 위의 response container를(timestamp, status ... path를 가진) 찾아보자

    힌트는 아래에 있다. (IDE의 run 텝의 메세지이다)

    2020-08-09 14:10:45.509  WARN 7580 --- [nio-8080-exec-1] 
    .w.s.m.s.DefaultHandlerExceptionResolver : 
    Resolved [org.springframework.web.bind.MethodArgumentNotValidException: 
    Validation failed for argument [0] 
    in public me.ndPrince.routeOptimization.model.DispatchResponse 
    me.ndPrince.routeOptimization.DispatchController.postDispatchRequest
    (me.ndPrince.routeOptimization.model.DispatchRequest): 
    [Field error in object 'dispatchRequest' on field 'rides': 
    rejected value [null]; codes [NotNull.dispatchRequest.rides,NotNull.rides,
    NotNull.java.util.List,NotNull]; arguments 
    [org.springframework.context.support.DefaultMessageSourceResolvable: 
    codes [dispatchRequest.rides,rides]; arguments []; default message [rides]]; 
    default message [this field must not be null]] ]

    두가지를 배운다.

    1) DefaultHandlerExceptionResolver Class가 spring validation 을 관장한다는 점

    2) 우리의 경우에 MethodArgumentNotValidException 이라고 불리는 exception이 발생한다는 점

     

    3. ResponseEntityExceptionHandler

    다음의 @ControllerAdvice 클래스를 만들자

    package me.ndPrince.routeOptimization.exception;
    
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.context.request.WebRequest;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
    
    import java.util.Date;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    @ControllerAdvice
    public class ValidationAdvisor extends ResponseEntityExceptionHandler {
    
        @Override
        protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                      HttpHeaders headers,
                                                                      HttpStatus status, WebRequest request) {
            Map<String, Object> body = new LinkedHashMap<>();
            body.put("timestamp", new Date());
            body.put("status", status.value());
    
            //Get all errors
            List<String> errors = ex.getBindingResult()
                    .getFieldErrors()
                    .stream()
                    .map(x -> x.getDefaultMessage())
                    .collect(Collectors.toList());
    
            body.put("errors", errors);
    
            return new ResponseEntity<>(body, headers, status);
        }
    }
    

    @ControllerAdvice가 생소하다

     @ControllerAdvice 의 javadoc 설명은 다음과 같다
     
     * Specialization of {@link Component @Component} for classes that declare
     * {@link ExceptionHandler @ExceptionHandler}, {@link InitBinder @InitBinder}, or
     * {@link ModelAttribute @ModelAttribute} methods to be shared across
     * multiple {@code @Controller} classes.
     
     @Controller 클래스들에 자주 사용되는 1)ExceptionHandler 2)InitBinder 3)ModelAttribute 등의
     메소드를 좀 더 구체화한 @Component이다

    컨트롤러 어드바이스 이외에, MethodArgumentNotValidException의 행동을 정의해주었다.

    getDefaultMessage에서는 어노테이션의 메세지 값을 가져온다 @NotNull(message="this field must not be null")

     

    4. 다시 Postman으로 확인

    내가 명시한 메세지가 나온다. 이로써 validation response에서 어느 정도의 customize를 해보았다.

    timestamp, status, errors 이외에 좀 더 많은 정보를 제공할 수 없을까?

    물론 위의 ResponseEntity<Object>에 더 많은 key,value 값을 넣을 수 있지만, 코드가 길어지고 재 사용성이 떨어진다.

     

    5. Custom Exception Handler 정의하기

    3번에서 보았던 extends을 쓰는 ControllerAdvice 말고 이렇게도 작성 할 수 있다.

    package me.ndPrince.routeOptimization.exception;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    import java.util.Date;
    
    @ControllerAdvice
    public class ValidationAdvisorSimplified {
    
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(MethodArgumentNotValidException.class)
        @ResponseBody
        public ValidationErrorResponse handleValidationExceptions(){
            return ValidationErrorResponse.builder()
                    .timestamp(new Date().toString())
                    .build();
        }
    }
    

    이 방법의 장점은 (1) 코드가 깔끔하다는 것이고,

    (2) exceptionHandler를 extends하는 것이 아니라 override를 사용 할 필요가 없고, 또한 response object를 responseEntity가 아닌, 내가 직접 구현 할 수 있다는 점이다.

     

    최대한 간단하게 만든 ValidationErrorResponse 클래스는 다음과 같다

    package me.ndPrince.routeOptimization.exception;
    
    import lombok.Builder;
    import lombok.Data;
    import org.springframework.boot.context.properties.bind.validation.ValidationErrors;
    
    import java.util.List;
    
    @Builder
    @Data
    public class ValidationErrorResponse {
        private String timestamp;
        private List<ValidationErrors> validationErrorsList;
    }
    

    postman의 결과값도 확인해보자 (3번에서 만든 ControllerAdvice는 잊지말고 comment out 시키자)

    우리가 정의한 response class를 사용하여 api response가 제공되었다.

    다음 시간에는 validationErrorList에 좀 더 많은 정보를 제공해보자.

     

    6. 혹시나 궁금할 ExceptionHandler의 종류

    ResponseEntityExceptionHandler 클래스를 살펴보자

    다음 두가지를 알 수 있다.

    1) Webmvc에서 제공되는 클래스이다 (화면에는 안나왔지만.. 경로를 보면 그렇다..)

    2) 해당 클래스에는 다음의 꽤 많은 exception들이 존재한다

     

    소스코드

    https://github.com/2ndPrince/routeOptimization/tree/spring_validation_2

Designed by Tistory.