@Valid 어노테이션이 javax.validation 패키지에 속하는 반면, @Validated 어노테이션은 org.springframework.validation.annotation에 속합니다.
https://wildeveloperetrain.tistory.com/158 이 링크 참조
@Valid -> Controller에서 검증가능, MethodArgumentNotValidException
발생
@Validated -> Controller 외부에서 가능 (AOP기반) , ConstraintViolationException
발생
왠만하면 컨트롤러 단에서 다 검증해주는 것이 좋다.
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class Person {
@NotNull
@Size(max = 64)
private String name;
/* 생략 */
}
컨트롤러 메서드에 @Valid로 시작하는 어노테이션을 붙여준다.
@PostMapping(value = "/", produces = "application/json")
public ResponseEntity<?> sendSMS(@Valid @RequestBody Person person) {
/* 생략 */
}
public class SignUpRequest {
@Valid private Email email;
@Valid private Name name;
}
public class Name {
@NotEmpty private String first;
private String middle;
@NotEmpty private String last;
}
public class Email {
@javax.validation.constraints.Email
private String value;
}
이런식으로 다른 클래스를 멤버 변수로 가지고 있는 경우
상위 클래스에 @Valid를 붙이고 아래와 같이 호출하면 된다.
@RestController
@RequestMapping("/members")
public class MemberApi {
private final MemberSignUpService memberSignUpService;
@PostMapping
public MemberResponse create(@RequestBody @Valid final SignUpRequest dto) {
final Member member = memberSignUpService.doSignUp(dto);
return new MemberResponse(member);
}
}
모든 요청은 프론트 컨트롤러인 디스패처 서블릿을 통해 컨트롤러로 전달된다.
전달 과정에서 컨트롤러에서 @ResponseBody 어노테이션을 사용하는 경우 ArgumentResolver의 구현체인 RequestResponseBodyMethodProcessor에 의해 @Valid로 시작하는 어노테이션이 있을 경우 유효성 검사를 진행한다.
(이러한 이유로 커스텀 어노테이션도 @Valid로만 시작한다면 유효성 검사를 진행한다.)
만약 유효성 검사 중 Constrain에 맞지 않는 것이 있다면,
MethodArgumentNotValidException
예외가 발생하게 되고, 이는 디스패처 서블릿에 기본으로 등록된 기본 Exception Resolver인 DefaltHandlerExceptionResolver에 의해 400 BadRequest 에러를 발생시킨다.
@Valid 는 기본적으로 컨트롤러에서만 동작하며 다른 계층에서는 검증이 되지 않는다.
다른 계층에서 파라미터를 검증하기 위해서는 @Validated 와 결합되어야한다.
JSR 표준 기술이 아니며 Spring 프레임 워크에서 제공하는 어노테이션이다.
다음과 같이 클래스에 @Validated를 붙여주고 유효성 검증할 메서드의 파라미터에 @Valid를 붙여주면 된다.
@Service
@Validated
public class UserService {
public void addUser(@Valid AddUserRequest addUserRequest) {
...
}
}
여기서 @Validated는 @Valid의 MethodArgumentNotValidException
이 아닌, ConstraintViolationException
을 발생시키며,
ArgumentResolver에 의해 진행되었던 @Valid와는 달리 AOP기반으로 MethodValidationInterceptor를 통해 진행된다.
동일한 클래스에 대해 제약조건이 요청에 따라 달라질 수 있다.
예를 들어 일반 사용자의 요청과 관리자의 요청이 1개의 클래스로 처리 될때, 각각 다른 제약 조건이 적용되어야 할 수 있다.
검증 그룹을 지정하기 위해서는 내용이 없는 마커 인터페이스를 간단히 정의 해야한다.
public interface UserValidationGroup {}
public interface AdminValidationGroup {}
그리고 제약조건에 groups로 지정해 줄 수 있다.
@NotEmpty(groups = {UserValidationGroup.class, AdminValidationGroup.class} )
private String name;
@NotEmpty(groups = UserValidationGroup.class)
private String userId;
@NotEmpty(groups = AdminValidationGroup.class)
private String adminId;
컨트롤러에도 제약조건 검증을 적용할 클래스를 지정해주면 된다.
@PostMapping("/users")
public ResponseEntity<Void> addUser(
@RequestBody @Validated(UserValidationGroup.class) AddUserRequest addUserRequest) {
...
}
그 다음 @Validated의 파라미터로 그룹을 지정해주면 된다.
https://mangkyu.tistory.com/174
https://wildeveloperetrain.tistory.com/158
https://gaemi606.tistory.com/entry/Spring-Boot-ResponseBody-%EA%B0%81-%ED%95%AD%EB%AA%A9%EC%97%90-%ED%81%AC%EA%B8%B0-%ED%95%84%EC%88%98-%EA%B0%92-%EC%84%A4%EC%A0%95-spring-boot-starter-validation