JVM/Spring

[SPRING] @Valid 어떻게 동작할까 - java bean validation

Hyo Kim 2022. 3. 26. 14:44
728x90
반응형

😐서론

애플리케이션 개발을 진행할 때 검증은 가장 중요한 작업이다.

클라이언트에서 데이터가 제대로 넘어왔는지,

비지니스 로직에서 인수가 제대로 된 값이 넘어왔는지,

db에 값이 저장 혹은 수정되기 전에 제대로 된 값이 맞는지 등

정말 다양한 곳에서 데이터 검증이 필요하며 놓쳐서는 안되는 중요한 작업이다.

 

이 검증을 쉽게 할 수 있는 @Valid 에 대해서 알아보았다.

 

목차

- @Valid 는 뭐지?

- @Valid 왜 사용하는거지?

- @Valid 어떻게 사용하는거지?

- Jakarta Validation API 살펴보기

- Jakarta Validation API 구현체 찾기 - Hibernate Validator

- Spring 에서 @Valid 동작 원리


🙄본론

@Valid 는 뭐지?

JSR 303부터 현재 JSR 380 버전으로, 자바 표준 스펙으로 지정되어 있는 Bean Validation 기능 입니다.

 

Jakarta Bean Validation 2.0 - Java 8 이상

Jakarta Bean Validation 3.0 - Java 11 이상

 

Jakarta Bean Validation 에 가시면 더욱 상세한 정보를 확인할 수 있습니다.


@Valid 왜 사용하는거지?

1. 간단한 검증을 하더라도 검증관련 로직이 길어지게 됩니다.

if (itemDTO == null) {
    throw new IllegalArgumentException("아이템 정보가 존재하지 않습니다.");
}

if (itemDTO.getItemName() == null || itemDTO.getItemName().isEmpty()) {
    throw new IllegalArgumentException("아이템 명은 필수 입니다.");
}

if (itemDTO.getQuantity() == null) {
    throw new IllegalArgumentException("아이템 개수는 필수 입니다.");
}

if (itemDTO.getQuantity() < 1) {
    throw new IllegalArgumentException("아이템 개수는 1 이상 이어야 합니다.");
}

if (itemDTO.getQuantity() > 100) {
    throw new IllegalArgumentException("아이템 개수는 100 이하여야 합니다.");
}

2. 검증 로직이 중복으로 여러 Layer에 존재하게 됩니다.

3. Layer에 검증 로직이 섞여있기 때문에 추적이 어렵고, 애플리케이션이 복잡해집니다.

https://docs.jboss.org/hibernate/validator/8.0/reference/en-US/html_single/#preface

 

@Valid 사용 시

1. 검증 로직을 통합하고, Layer에서 분리하여 관리할 수 있게 됩니다.

public ResponseEntity<String> save(@Valid @RequestBody ItemDTO itemDTO) {
    itemService.save(itemDTO);
    return new ResponseEntity<>(HttpStatus.CREATED);
}

https://docs.jboss.org/hibernate/validator/8.0/reference/en-US/html_single/#preface

Hibernate Validator 에 가시면 더욱 상세한 정보를 확인할 수 있습니다.


@Valid 어떻게 사용하는거지?

Spring Boot 사용 시 아래 의존성을 추가해주시면 됩니다.

// Gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'

// Maven
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

spring boot starter validation

Hibernate ValidatorJakarta Bean Validation의 구현체 입니다.

Jakarta Bean Validation 는 명세이기 때문에 따로 구현체를 연결해서 사용하고 있고,

Hibernate Validator 를 기본으로 사용하고 있습니다.

 

Hibernate Validator만 추가해주어도, Jakarta Bean Validation API 에 대한 의존성이 추가가 됩니다.


Jakarta Validation API 살펴보기

jakarta validation

기본적으로 정의해둔 어노테이션 목록을 확인할 수 있습니다.

NotNull 어노테이션 정보

여기서 살펴보아야 하는 것은 @Constraint 어노테이션 입니다.

@Constraint 어노테이션에 있는 validateBy 정보에 구현체를 넣어서 검증이 진행되는 방식입니다.

 

하지만, jakarta validation 에 기본 구성으로 나와있는 어노테이션들은 모두 구현체 정보가 존재하지 않습니다.

 

그러면 어디서 구현체를 찾아가 보도록 하겠습니다.


Jakarta Validation API 구현체 찾기 - Hibernate Validator

맨 처음 의존성을 추가할 때 말씀드렸다시피 hibernate-validator 가 구현체 입니다.

 

그러면, 구현체는 어디에 정의되어 있을까요??

org.hibernate.validator.internal.constraintvalidators.bv

 

@NotNull 검증 클래스

아까 jakarta validation 에서 살펴본 @NotNull 검증 로직이 여기에 있는 것을 확인할 수 있습니다.

ConstraintValidator 인터페이스에 isValid를 구현하여 검증 로직을 만들 수 있게 됩니다.

 

그렇다면, jakarta validation 어노테이션과 hibernate 검증 클래스를 어디서 연결해줄까요??

org.hibernate.validator.internal.metadata.core.ConstraintHelper.java

 

ConstraintHelper.java

구현체와 어노테이션을 연결해주는 부분을 찾았습니다. (다른 어노테이션 정보들도 여기서 연결하고 있습니다.)

결국, jakarta.validation 에서는 표준 어노테이션을 정의만 해두고, 해당 표준 어노테이션을 가져다가

hibernate.validator 에서 구현체를 만들어서 연결을 해주고 있는 걸 볼 수 있었습니다.

 

그럼 이제 Spring boot 에서는 해당 어노테이션을 어떻게 사용하고 있는지 확인해보도록 하겠습니다.


Spring 에서 @Valid 동작 원리

controller 동작

Controller 로 요청이 들어오면, Dispatcher Servlet을 걸쳐서 Controller가 호출이 됩니다.

이 과정에서 Dispatcher Servlet 에서 @Valid 를 찾아서 검증을 진행하게 됩니다.

 

간략하게 살펴보면 아래와 같은 흐름을 띄웁니다.

1. 클라이언트에서 컨트롤러 호출

2. Dispatcher Servlet 에서 요청에 맞는 컨트롤러 탐색

3. RequestResponseBodyMethodProcessor 클래스의 resolverArgument 메소드 호출

RequestResponseBodyMethodProcessor.java

4. AbstractMessageConverterMethodArgumentResolver 클래스의 validateIfApplicable 메소드 호출

AbstractMessageConverterMethodArgumentResolver.java

5. ValidationAnnotationUtils 클래스의 determineValidationHints 메소드 호출

5-1. 어노테이션이 java.validation.Valid 인지, Validated인지, Valid 로 시작하는지 확인 후 Object Array 리턴

ValidationAnnotationUtils.java

6. DataBinder -> ValidatorAdapter -> SpringValidatorAdapter 를 통해

   hibernate.validator의 ValidatorImpl 클래스 호출

6-1. ValidatorImpl 에서 검증 후 결과 리턴

 

7. 검증 결과 Errors 에 값이 있을 시 MethodArgumentNotValidException 발생

 

그래서 여기서 알 수 있는 것은?

- @Valid 은 Dispatcher Servlet 에서 사용하고 있다.

- @Valid 은 컨트롤러에서 밖에 사용할 수 없다.

 

이후 글 - [SPRING] @Valid @Validated 사용하기 - java bean validation


참고

https://jyami.tistory.com/55
https://kapentaz.github.io/spring/Spring-Boo-Bean-Validation-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90/#
https://meetup.toast.com/posts/223
https://stackoverflow.com/questions/36477544/javax-validation-implementation
https://gompangs.tistory.com/entry/Spring-Valid-Collection-Validation-%EA%B4%80%EB%A0%A8
https://mangkyu.tistory.com/174
https://en.wikipedia.org/wiki/Bean_Validation
728x90
반응형