스프링을 사용할 때 HTTP 요청에 포함된 데이터를 DTO를 통해 객체 형태로 받거나, 메서드의 매개변수로 직접 전달받을 수 있다.
애플리케이션 내에서는 도메인에 따라 전달받은 데이터를 검증해야 하는 경우가 있다.
public ResponseEntity xxxService(MemberRequest memberRequest) {
String name = member.name();
if (name == null || name.isEmpty()){
throw new ...
}
if (name.length() > 255) {
throw new ...
}
}
전달받은 데이터를 Service 클래스에서 검증하는 예시이다.
이렇게 Service 계층에서 예외를 처리하게 되면 예외 처리 로직과 비즈니스 로직이 섞이는 단점이 있다.
또한, null
, 빈 문자열(""
)과 같은 단순 검증을 반복적으로 작성해야 하는 문제가 발생한다.
spring-boot-starter-validation 의존성을 추가하면 Bean Validation을 사용할 수 있다.
기본적으로 Bean Validation은 객체 필드에 검증 애노테이션을 달아 제약 조건을 정의하는 방식으로 동작한다.
다음 3가지 요청 데이터에 대해 유효성 검증을 할 수 있다.
간단히 @Valid는 자바에서 제공, @Validated는 스프링에서 제공한다고 생각하면 된다.
디스패처 서블릿에서 컨트롤러로 요청이 전달될 때, @RequestBody
가 붙은 파라미터는 JSON 형식의 데이터를 Java 객체로 자동 매핑한다.
이때 @Valid
와 필드에 검증 애노테이션이 있으면 해당 필드에 대한 유효성을 검사한다.
유효성 검사에서 실패하는 경우 MethodArgumentNotValidException
예외가 발생하며,
기본적으로 Spring은 이 예외를 HTTP 상태 코드 400 (Bad Request)
로 변환한다.
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<List<String>> handleInvalidFormatException(final MethodArgumentNotValidException exception) {
List<String> errors = new ArrayList<>();
exception.getBindingResult().getFieldErrors().forEach(fieldError -> {
errors.add("["+ fieldError.getField() + "] " + fieldError.getDefaultMessage());
});
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(errors);
}
위와 같이 해당 예외를 처리할 수 있다.
이는 클라이언트에게 상세한 오류 정보를 제공하는 방식의 예시이다.
스프링에서 제공하는 기능으로, 클래스 레벨에 @Validated
를 적용하고 파라미터에 @Valid
를 명시하면 해당 객체에 대해 검증을 진행할 수 있다.
@Valid
와 달리 클래스 레벨에 @Validated
를 설정하기 때문에, 컨트롤러 이외의 클래스에서도 파라미터 유효성 검사가 가능하다.
또한, @Validated
는 유효성 검증 그룹을 지정할 수 있는 기능을 제공하지만, 일반적으로 자주 사용되지 않으므로 여기서는 다루지 않는다.
유효성 검사에 실패하면 ConstraintViolationException
이 발생하며,
Spring이 기본적으로 해당 예외를 처리하지 않기 때문에 HTTP 상태 코드 500 (Internal Server Error)
로 변환된다.
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity constraintViolationException(final ConstraintViolationException exception) {
log.warn("message: ", exception);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("입력값이 유효하지 않습니다.");
}
위와 같이 해당 예외를 처리할 수 있다.
이는 클라이언트에게 많은 정보를 제공하지 않는 예시이다.