programing

spring @Valid Validation 기본 오류 메시지를 커스터마이즈하려면 어떻게 해야 하나요?

starjava 2023. 3. 18. 08:13
반응형

spring @Valid Validation 기본 오류 메시지를 커스터마이즈하려면 어떻게 해야 하나요?

DTO:

public class User {

    @NotNull
    private String name;

    @NotNull
    private String password;

    //..
}

컨트롤러:

@RequestMapping(value = "/user", method = RequestMethod.POST)
public ResponseEntity<String> saveUser(@Valid @RequestBody User user) {
    //..
    return new ResponseEntity<>(HttpStatus.OK);
}

기본 json 오류:

{"timestamp":1417379464584,"status":400,"error":"Bad Request","exception":"org.springframework.web.bind.MethodArgumentNotValidException","message":"Validation failed for argument at index 0 in method: public org.springframework.http.ResponseEntity<demo.User> demo.UserController.saveUser(demo.User), with 2 error(s): [Field error in object 'user' on field 'name': rejected value [null]; codes [NotNull.user.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [may not be null]],"path":"/user"}

에러마다 커스텀 json이 발생했으면 합니다.어떻게 하면 될까요?

모든 컨트롤러의 응답 메시지를 완전히 제어하려면ControllerAdvice예를 들어, 이 변환 예는MethodArgumentNotValidException커스텀 json 오브젝트로 변환합니다.

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.validation.BindingResult;
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.ArrayList;
import java.util.List;

import static org.springframework.http.HttpStatus.BAD_REQUEST;

/**
 * Kudos http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
 *
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class MethodArgumentNotValidExceptionHandler {

    @ResponseStatus(BAD_REQUEST)
    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Error methodArgumentNotValidException(MethodArgumentNotValidException ex) {
        BindingResult result = ex.getBindingResult();
        List<org.springframework.validation.FieldError> fieldErrors = result.getFieldErrors();
        return processFieldErrors(fieldErrors);
    }

    private Error processFieldErrors(List<org.springframework.validation.FieldError> fieldErrors) {
        Error error = new Error(BAD_REQUEST.value(), "validation error");
        for (org.springframework.validation.FieldError fieldError: fieldErrors) {
            error.addFieldError(fieldError.getField(), fieldError.getDefaultMessage());
        }
        return error;
    }

    static class Error {
        private final int status;
        private final String message;
        private List<FieldError> fieldErrors = new ArrayList<>();

        Error(int status, String message) {
            this.status = status;
            this.message = message;
        }

        public int getStatus() {
            return status;
        }

        public String getMessage() {
            return message;
        }

        public void addFieldError(String path, String message) {
            FieldError error = new FieldError(path, message);
            fieldErrors.add(error);
        }

        public List<FieldError> getFieldErrors() {
            return fieldErrors;
        }
    }
}

Errors/BindingResult 개체를 사용하여 검증을 수행할 수 있습니다.컨트롤러 메서드에 Errors 인수를 추가하고 오류가 발견되면 오류 메시지를 사용자 지정합니다.

다음으로 검증에 실패했을 때 true를 반환하는 errors.hasErrors()의 예를 나타냅니다.

@RequestMapping(value = "/user", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<String> saveUser(@Valid @RequestBody User user, Errors errors) {
    if (errors.hasErrors()) {
        return new ResponseEntity(new ApiErrors(errors), HttpStatus.BAD_REQUEST);
    }
    return new ResponseEntity<>(HttpStatus.OK);
}

오래된 질문인 건 알지만

그런데 우연히 발견했는데 꽤 괜찮은 기사가 있더군요. github에도 완벽한 예가 있어요.

기본적으로는@ControllerAdvice봄철 문서에서 알 수 있듯이.

예를 들어 400개의 오류를 검출하려면 다음 중 하나의 함수를 덮어씁니다.

@ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
        logger.info(ex.getClass().getName());
        //
        final List<String> errors = new ArrayList<String>();
        for (final FieldError error : ex.getBindingResult().getFieldErrors()) {
            errors.add(error.getField() + ": " + error.getDefaultMessage());
        }
        for (final ObjectError error : ex.getBindingResult().getGlobalErrors()) {
            errors.add(error.getObjectName() + ": " + error.getDefaultMessage());
        }
        final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errors);
        return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request);
    }
}

(ApiError 클래스는 상태, 메시지, 오류를 유지하는 단순한 개체입니다.)

이를 위한 한 가지 방법은 엔티티 속성의 @NotNull 주석에 메시지를 추가하는 것입니다.컨트롤러 요청 본문에 @Valid 주석을 추가합니다.

DTO:

public class User {
   
    @NotNull(message = "User name cannot be empty")
    private String name;

    @NotNull(message = "Password cannot be empty")
    private String password;

    //..
}

컨트롤러:

@RequestMapping(value = "/user", method = RequestMethod.POST)
public ResponseEntity<String> saveUser(@Valid @RequestBody User user) {
    //..
    return new ResponseEntity<>(HttpStatus.OK);
}
// Add one 
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<List<YourErrorResponse>> handleException(MethodArgumentNotValidException ex) {
// Loop through FieldErrors in ex.getBindingResult();
// return *YourErrorReponse* filled using *fieldErrors*
}
@ControllerAdvice(annotations = RestController.class)
public class GlobalExceptionHandler implements ApplicationContextAware {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ApplicationError validationException(MethodArgumentNotValidException e) {

        e.printStackTrace();
        return new ApplicationError(SysMessageEnum.MSG_005, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());

    }

}

이런 거 할 수 있어요.

@ExceptionHandler(value = MethodArgumentNotValidException.class)
      protected ResponseEntity<Error> handleGlobalExceptions(MethodArgumentNotValidException ex,
          WebRequest request) {
        log.catching(ex);
        return new ResponseEntity<>(createErrorResp(HttpStatus.BAD_REQUEST,
            ex.getBindingResult().getFieldErrors().stream().map(err -> err.getDefaultMessage())
                .collect(java.util.stream.Collectors.joining(", "))),
            HttpStatus.BAD_REQUEST);
      }

JSON 형식의 에러 메시지를 커스터마이즈 하려면 , 다음의 순서를 실행합니다.

- CommonErrorHandler라고 하는1개의 @Component를 만듭니다.

@Component
public class CommonErrorHandler {
public  Map<String,Object> getFieldErrorResponse(BindingResult result){

        Map<String, Object> fielderror = new HashMap<>();
        List<FieldError>errors= result.getFieldErrors();
        for (FieldError error : errors) {
            fielderror.put(error.getField(), error.getDefaultMessage());
        }return fielderror;
    }

     public ResponseEntity<Object> fieldErrorResponse(String message,Object fieldError){
        Map<String, Object> map = new HashMap<>();
        map.put("isSuccess", false);
        map.put("data", null);
        map.put("status", HttpStatus.BAD_REQUEST);
        map.put("message", message);
        map.put("timeStamp", DateUtils.getSysDate());
        map.put("filedError", fieldError);
        return new ResponseEntity<Object>(map,HttpStatus.BAD_REQUEST);
    }
}

-- Invalid Exception 클래스 추가

public class InvalidDataException extends RuntimeException {

/**
 * @author Ashok Parmar
 */
    private static final long serialVersionUID = -4164793146536667139L;

    private BindingResult result;

    public InvalidDataException(BindingResult result) {
        super();
        this.setResult(result);
    }

    public BindingResult getResult() {
        return result;
    }

    public void setResult(BindingResult result) {
        this.result = result;
    }

}

- @ControllerAdvice 클래스 소개

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

@ExceptionHandler(InvalidDataException.class)
    public ResponseEntity<?> invalidDataException(InvalidDataException ex, WebRequest request) {

        List<FieldError> errors = ex.getResult().getFieldErrors();
        for (FieldError error : errors) {
            logger.error("Filed Name ::: " + error.getField() + "Error Message :::" + error.getDefaultMessage());
        }
        return commonErrorHandler.fieldErrorResponse("Error", commonErrorHandler.getFieldErrorResponse(ex.getResult()));
    }
    }

-- 컨트롤러에서 @Valid 및 through 예외와 함께 사용합니다.

public AnyBeans update(**@Valid** @RequestBody AnyBeans anyBeans ,
            BindingResult result) {
        AnyBeans resultStr = null;
        if (result.hasErrors()) {
            **throw new InvalidDataException(result);**
        } else {
                resultStr = anyBeansService.(anyBeans );
                return resultStr;
        }
    }

-- 출력은 JSON 형식입니다.

{
  "timeStamp": 1590500231932,
  "data": null,
  "message": "Error",
  "isSuccess": false,
  "status": "BAD_REQUEST",
  "filedError": {
    "name": "Name is mandatory"
  }
}

이것이 효과가 있기를 바랍니다. :-D

@ControllerAdvice
@RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

        @Override
        protected ResponseEntity<Object> handleMethodArgumentNotValid(
                MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {

           // ex.getBindingResult(): extract the bind result for default message. 
              String errorResult = ex.getBindingResult().toString();
             CustomizedExceptionHandlerResponse exceptionResponse = new CustomizedExceptionHandlerResponse(
                    errorResult, new Date(), request.getDescription(false));

            return new ResponseEntity<>(exceptionResponse, HttpStatus.BAD_REQUEST);
        }


}

class CustomizedExceptionHandlerResponse {

   private String message;
   private String status;
   private Date timestamp;

   // constuctor, setters, getters...
}

이 코드를 사용하여 오류를 반복하고 커스텀에러 메시지를 작성할 수 있습니다.

import lombok.Data;
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.bind.annotation.ExceptionHandler;

import javax.validation.ConstraintViolation;
import java.util.List;
import java.util.stream.Collectors;

@ControllerAdvice
public class ExceptionHandlerController {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorDto> handleException(MethodArgumentNotValidException ex) {

        ErrorDto dto = new ErrorDto(HttpStatus.BAD_REQUEST, "Validation error");

        dto.setDetailedMessages(ex.getBindingResult().getAllErrors().stream()
            .map(err -> err.unwrap(ConstraintViolation.class))
            .map(err -> String.format("'%s' %s", err.getPropertyPath(), err.getMessage()))
            .collect(Collectors.toList()));

        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(dto);

    }

    @Data
    public static class ErrorDto {

        private final int status;
        private final String error;
        private final String message;
        private List<String> detailedMessages;

        public ErrorDto(HttpStatus httpStatus, String message) {
            status = httpStatus.value();
            error = httpStatus.getReasonPhrase();
            this.message = message;
        }

    }

}

그러면 오류가 발생한 경우 다음과 같은 응답이 나타납니다.

{
  "status": 400,
  "error": "Bad Request",
  "message": "Validation error",
  "detailedMessages": [
    "'yourField' should not be empty."
  ]
}

정보도 추가해 주세요.그냥 사용하면@Valid, 당신은 캐치할 필요가 있다.BindException를 사용하는 경우@Valid @RequestBody또 만나MethodArgumentNotValidException

일부 소스:
HandlerMethodArgumentResolverComposite.getArgumentResolver(MethodParameter parameter):129- 어떤 Handler Method Argument Resolver가 이러한 파라미터를 지원하는지 검색합니다. RequestResponseBodyMethodProcessor.supportsParameter(MethodParameter parameter)- 파라미터에 주석이 있는 경우 true를 반환합니다. @RequestBody

RequestResponseBodyMethodProcessor:139- Method Argument Not Valid Exception을 슬로우합니다. ModelAttributeMethodProcessor:164- Bind Exception을 슬로우합니다.

언급URL : https://stackoverflow.com/questions/33663801/how-do-i-customize-default-error-message-from-spring-valid-validation

반응형