참고: @Valid와 @Validated를 이용한 유효성 검증의 동작 원리 및 사용법 예시
Validation
올바르지 않은 데이터를 걸러내고, 보안을 유지하기 위해 데이터 검증은 여러 계층에 걸쳐서 적용된다.
클라이언트의 데이터는 조작이 쉽고 모든 데이터가 정상적인 방식으로 들어오는 것이 아니기 때문에 클라이언트 측 뿐만 아니라 서버 측에서도 데이터 유효성을 검사할 필요가 있다.
✅ @Valid 와 @Validated 차이
@Valid | @Validated | |
JSR-303 자바 표준 검증 어노테이션 | 스프링 프레임워크에서 제공하는 어노테이션 | |
적용 계층 | 컨트롤러 | 컨트롤러 + 다른 계층도 가능 |
유효성 검증 실패 시 발생하는 예외 | MethodArgumentNotValidException | ConstraintViolationException |
특징 | @Valid 기능 + 제약 조건이 적용될 검증 그룹을 지정할 수 있다. |
1️⃣ @Valid
Bean Validator에게 객체의 제약 조건을 검증하도록 지시하는 어노테이션이다.
- JSP 표준 기술
- 객체 필드에 어노테이션을 달아주기만 하면 LocalValidatorFactoryBean가 제약 조건 검증을 처리한다.
- 스프링부트에서 사용하기 위해서는 아래 의존성을 추가해줘야 한다.
- implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation'
- @Valid는 기본적으로 컨트롤러에서만 동작하면 다른 계층에서는 검증되지 않는다.
사용 예시
// DTO
// email, password, address 필드에 대해 유효성 검증을 수행
@Date
public class UserRequest {
@Email
private String email;
@NotBlank
private String password;
@NotNull
private Address address;
}
// 컨트롤러
// 메서드 인자에 @Valid 지정 > 요청이 들어오면 해당 인자에 대한 유효성 검사 실행
@PostMapping("/sign-up")
public String signUp(@Valid @RequestBody UserRequest request) {
return "ok";
}
동작 원리
- 모든 요청은 프론트 컨트롤러인 디스패처 서블릿을 통해 컨트롤러로 전달된다. 전달 과정에서는 컨트롤러 메서드의 객체를 만들어주는 ArgumentResolver가 동작하는데, @Valid 역시 ArgumentResolver에 의해 처리가 된다.
- @RequestBody는 Json 메세지를 객체로 변환해 주는 작업이 ArgumentResolver의 구현체 인 RequestResponseBodyMethodProcessor가 처리한다., 이 내부에서 @Valid로 시작하는 어노테이션이 있을 경우에 유효성 검사를 진행한다. 만약 @ModelAttribute를 사용 중이라면 ModelAttributeMethodProcessor에 의해 @Valid가 처리된다.
- 검증에 오류가 있다면 MethodArgumentNotValidException 예외가 발생하게 되고, 디스패처 서블릿에 기본으로 등록된 Exception Resolver인 DefaultHandlerExceptionResolver에 의해 400 BadRequest 에러가 발생한다.
→ 이런 이유로 @Valid는 기본적으로 컨트롤러에서만 동작하고, 다른 계층에서는 검증되지 않는다.
→ 다른 계층 파라미터를 검증하기 위해서는 @Validated를 사용한다.
2️⃣ @Validated
컨트롤러가 아닌 다른 계층에서 파라미터를 검증해야 하는 경우에 사용한다.
- AOP 기반으로 메소드의 요청을 가로채서 유효성 검증을 진행
- JSR 표준 기술이 아니며 Spring 프레임워크에서 제공하는 어노테이션 및 기능
- 추가로 제약 조건이 적용될 검증 그룹을 지정할 수 있다.
- 유효성 검증에 실패하면 ConstraintViolationException 예외가 발생한다.
사용 예시
클래스에 @Validated를 붙이고, 유효성을 검증할 파라미터에 @Valid를 붙여주면 된다.
@Service
@Validated
public class UserService {
public void addUser(@Valid AddUserRequest addUserRequest) {
...
}
}
동작 원리
- 클래스에 유효성 검증 AOP가 적용되도록 @Validated를, 검증을 진행할 메서드에는 @Valid를 선언 > 해당 클래스에 유효성 검증을 위한 AOP의 어드바이스 또는 인터셉터(MethodValidationInterceptor)가 등록된다.
- 그러면 해당 클래스의 메소드가 호출될 때 AOP의 포인트 컷으로써 요청을 가로채서 유효성 검증을 진행한다.
→ 이러한 이유로 @Validated를 사용하면 컨트롤러, 서비스, 레포지토리 등 계층에 무관하게 스프링 빈이라면 유효성 검증을 진행할 수 있다.
→ 이런 이유로 @Valid에 의한 예외는 MethodArgumentNotValidException이며, @Validated에 의한 예외는 ConstraintViolationException이다.
@Validated의 유효성 검증 그룹 지정
동일한 클래스라도 요청에 따라 유효성 검증 제약 조건이 달라질 수 있다.
ex) 일반 사용자 요청 vs 관리자 요청
- @Validated에서 제약 조건이 적용될 그룹을 지정할 수 있다.
사용 예시
1. 마커 인터페이스 정의 (내용이 없는 인터페이스)
public interface UserValidationGroup {} // 사용자
public interface AdminValidationGroup {} // 관리자
2. 해당 제약 조건이 적용될 그룹을 groups로 지정
- 적용될 그룹이 여러 개라면 {} 안에 그룹 이름을 나열하면 된다.
// UserValidationGroup, AdminValidationGroup에만 유효성 검사
@NotEmpty(groups = {UserValidationGroup.class, AdminValidationGroup.class})
private String name;
// UserValidationGroup에만 유효성 검사
@NotEmpty(groups = UserValidationGroup.class)
private String userId;
// AdminValidationGroup에만 유효성 검사
@NotEmpty(groups = AdminValidationGroup.class)
private String adminId;
3. 유효성 검증을 적용할 파라미터에 지정
@PostMapping("/users")
public ResponseEntity<Void> addUser(
// UserValidationGroup 에 해당하는 제약 조건만 검증
@RequestBody @Validated(UserValidationGroup.class) AddUserRequest addUserRequest) {
...
}
만약 @Validated에 특정 마커 인터페이스를 지정해주지 않았거나, groups가 지정되어 있는데 적용할 파라미터에는 @Valid를 지정했다면 다음과 같이 처리된다.
- @Validated에 특정 클래스를 지정하지 않는 경우: groups가 없는 속성들만 처리
- @Valid or @Validated에 특정 클래스를 지정한 경우: 지정된 클래스를 groups로 가진 제약 사항만 처리
'CS > Backend' 카테고리의 다른 글
[Spring] JPA - 양방향 연관 관계 mappedBy, @JoinColumn (0) | 2023.07.18 |
---|---|
[Spring] 서블릿, HTTP request & response (0) | 2023.06.27 |
[Spring] Pagiantion, Pageable 인터페이스 (0) | 2023.06.27 |
[Java] 직렬화, 역직렬화 (0) | 2023.06.25 |
[Spring] REST, REST API (0) | 2023.06.23 |