사용자가 보낸 데이터 값이 백엔드에서 취급하는 데이터와 같은 형태인지 확인하는 것이다.
이렇게 유효성 검사를 하지 않으면 비즈니스 로직에서 오류가 발생하기 때문에 사전 차단하기 위함이다.
모든 요청은 필터 → 디스패처 서블릿 → 핸들러 → 컨트롤러의 경로를 통해 전달되는데
전달 과정에서 컨트롤러 메소드의 객체를 만들어주는 ArgumentResolver
가 동작한다.
@Valid
또한 ArgumentResolver
에 의해 처리된다.
유효성을 검사하는 과정에서 오류가 있다면 MethodArgumentNotValidException
예외가 발생하고, 디스패처 서블릿에 기본으로 등록된 예외 리졸버인 DefaultHandlerExceptionResolver
에 의해 400 BadRequest 에러가 발생한다.
@Valid
는 기본적으로 컨트롤러에서만 동작하며 기본적으로 다른 계층에서는 검증되지 않는다.
다른 계층에서 파라미터를 검증하기 위해서는 @Validated
와 결합하여야 한다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
왜 의존성을 추가해서 사용해야 하죠?
Hibernate Validator는 Jakarta Bean Validation 의 구현체이기 때문에 의존성을 추가하지 않는다면 어노테이션 추가는 가능하지만 사용하지 못할 것이다. (Jakarta Bean Validation 명세만 해둔 인터페이스)
@Constraint 애노테이션들이 기본적으로 지원되기 시작했다.
따라서 @Valid
또는 @Validated
를 붙여줄 필요없이 기본적인 유효성 검증이 동작한다.
입력 파라미터의 유효성 검증은 컨트롤러에서 최대한 처리하고 넘겨주는 것이 좋다. 하지만 개발을 하다 보면 항상 컨트롤러에서만 가능하지는 않을 것이다.
컨트롤러가 아닌 다른 곳에서는 어떻게 할까?
Spring에서는 AOP 기반으로 메소드의 요청을 가로채서 유효성 검증을 진행해주는 @Validated를 제공하고 있다. 아래 코드와 같이 클래스에 @Validated
를 사용하고 유효성을 검사할 파라미터에 @Valid
를 붙여준다.
@Service
@Validated
public class FooService {
public void addFoo(@Valid AddFooRequest addFooRequest) {
...
}
}
유효성 검사가 실패한다면?
이전 @Valid
에서는 MethodArgumentNotValidException
예외가 발생하지만 @Validated
의 경우에는 ConstraintViolationException
예외가 발생한다.
발생하는 예외 이름이 다른 것으로 보아 동작원리가 다르겠다는 짐작이 든다.
@Validated
는 스프링 전용 검증 애노테이션, @Valid
는 자바 표준 검증 애노테이션이다.
둘 중 아무거나 사용해도 동일하지만, @Validated
는 내부에 groups
라는 기능을 포함하고 있다.
동일한 모델 객체를 등록할 때와 수정할 때 각각 다르게 검증하는 방법을 알아보자
groups를 사용하기 위해서는 인터페이스를 만들어야 한다.
각 그룹별로 인터페이스 만들기
@NotNull(groups = UpdateCheck.class)
@NotBlank(groups= { SaveCheck.class, UpdateCheck.class})
그룹화 하면 각각 맞는 상황에 검증 로직을 실행한다.
하지만 사용하기 위해서는 해당하는 메소드 @validated(value = UpdateCheck.class)
를 컨트롤러에 적용해야 한다.
정리하면서도 느꼈지만, 너무 복잡하고 코드라 지저분해지기 때문에 자주 사용하지 않는다.
등록 폼을 받는 객체와 수정 폼을 받는 객체를 따로 분리해서 받는다면 이런 문제를 해결할 수 있다.
DTO를 사용해서 검증하는 객체를 분리하자!
클라이언트 사이드의 경우 @RequestBody
로 받는 JSON 객체 옆에 @Valid
어노테이션을 붙여서 사용 가능하다. 이 경우 세 가지의 경우로 분류할 수 있다.
Errors
, Result
등을 사용하여 에러를 처리할 수 있다.HTTP 요청 파라미터를 처리하는 @ModelAttribute
는 각각의 필드 단위로 세밀하게 적용되기 때문에 특정 필드의 타입이 맞지 않더라고 나머지 필드는 정상처리할 수 있다.
하지만 HttpMessageConverter
는 각 필드 단위로 적용되는 것이 아니라, 전체 객체 단위로 적용된다. 따라서 메시지 컨버터의 작동이 성공하지 못한 경우 @valid
, @Validate
가 적용된다.
@ModelAttribute
는 특정 필드가 바인딩 되지 않아도 나머지는 정상 바인딩되어 검증 적용 가능
@RequestBody
는 HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계 자체가 진행되지 않고 예외가 발생한다. 나머지 필드도 검증 불가능