💐 Spring/Spring 핵심 기술

12. Validation 추상화

2020. 7. 11. 19:03
목차
  1. Validation은 크게 두가지가 있다.
  2. 특징
  3. 인터페이스 : org.springframework.validation.Validator
  4. 스프링 부트 2.0.5 이상 버전을 사용할 때
  5. Spring boot Validation
  6. ExceptionHanlder를 이용해 에러를 알려주자
  7. Spring validator 인터페이스 구현을 통한 validation
  8. 생각해보기
  9. REFERENCE
반응형

Validation은 크게 두가지가 있다.

  • JavaBean 기반 Validation (가장 많이 사용)
  • Spring Validation 인터페이스 구현하여 Validation

 

org.springframework.validation.Validatior

애플리케이션에서 사용하는 객체 검증용 인터페이스

 

특징

  • 어떤 계층과도 관계가 없다. -> 모든 계층(웹, 서비스, 데이터) 에서 사용해도 좋다.
  • 구현체 중 하나로, JSR-303(Bean Validation 1.0), JSR-349(Bean Validation 1.1) 을 지원한다.(LocalValidatorFactoryBean)
  • DataBinder에 들어가 바인딩 할 때 같이 사용되기도 한다.

 

인터페이스 : org.springframework.validation.Validator

  • boolean support (Class clazz) : 어떤 타입의 객체를 검증할 때 사용할 것인지 결정함.
  • void validate(Object obj, Errors e) : 실제 검증 로직을 이 안에서 구현
    • 구현 할 때 ValidationUitils 사용하며 편리하다.

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/validation/Validator.html

 

Validator (Spring Framework 5.2.7.RELEASE API)

A validator for application-specific objects. This interface is totally divorced from any infrastructure or context; that is to say it is not coupled to validating only objects in the web tier, the data-access tier, or the whatever-tier. As such it is amen

docs.spring.io

 public class UserLoginValidator implements Validator {

    private static final int MINIMUM_PASSWORD_LENGTH = 6;

    public boolean supports(Class clazz) {
       return UserLogin.class.isAssignableFrom(clazz);
    }

    public void validate(Object target, Errors errors) {
       ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "field.required");
       ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "field.required");
       UserLogin login = (UserLogin) target;
       if (login.getPassword() != null
             && login.getPassword().trim().length() < MINIMUM_PASSWORD_LENGTH) {
          errors.rejectValue("password", "field.min.length",
                new Object[]{Integer.valueOf(MINIMUM_PASSWORD_LENGTH)},
                "The password must be at least [" + MINIMUM_PASSWORD_LENGTH + "] characters in length.");
       }
    }
 }

 

스프링 부트 2.0.5 이상 버전을 사용할 때

  • LocalValidatorFactoryBean 빈으로 자동 등록
  • JSR-380(Bean Validation 2.0.1) 구현체로 hibernate-validator 사용.
  • https://beanvalidation.org/

 

Spring boot Validation

스프링 2.3.0 이상 버전에서 더이상 Validation을 자동으로 가져오지 않기 때문에 아래 의존성 추가를 해준다.

<!-- 이 코드를 추가해준다! -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

 

DTO에 검증 조건 설정

public class CreateBookRequest {
	
    @NotBlank(message = "책 이름은 필수로 입력해야합니다.")
    @Size(max=50, message="최대 길이는 20입니다")
    private String name;
    
    @Min(0, "가격은 0원보다 커야합니다.")
	private int price;
    
    // ...
}

이런식으로 검증에 필요한 조건들을 넣어준다.

요청 Body로 들어오는 DTO 매개변수에 @Valid 어노테이션만 붙여서 훨씬 편리하게 사용할 수 있다.

@PostMapping("/books")
public CreateBookResponse createBook(@Valid @RequestBody CreateBookRequest) {
	// ...
}

 만약 검증 도중 실패하면 -> MethodArgumentNotValidException이 발생!

 

ExceptionHanlder를 이용해 에러를 알려주자

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(
  MethodArgumentNotValidException ex) {
    Map<String, String> errors = new HashMap<>();
    ex.getBindingResult().getAllErrors().forEach((error) -> {
        String fieldName = ((FieldError) error).getField();
        String errorMessage = error.getDefaultMessage();
        errors.put(fieldName, errorMessage);
    });
    return errors;
}

에러 발생 시:

{
	"name": "책 이름은 필수로 입력해야합니다.",
    "price": "가격은 0원보다 커야합니다."
}

Spring validator 인터페이스 구현을 통한 validation

  • supports: 이 validator가 동작할 조건을 정의, 주로 클래스 타입을 비교
  • validate: 원하는 검증을 진행한다.
public class EventValidator implements Validator {


    @Override
    public boolean supports(Class<?> clazz) {
        return Event.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "notEmpty", "Empty title is not allowed.");
        // validate할 field(title)가 empty or whitespace 일때, errorcode로 notEmpty를 출력, defaultMessage는 errorcode를 찾지 못했을때, 출력!
    }
}

 

package com.example.demospring51;

public class Event {
    Integer id;

    String title;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
@Component
public class AppRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Event event = new Event();
        EventValidator eventValidator = new EventValidator();
        Errors errors = new BeanPropertyBindingResult(event, "event");
        //어떤 객체를 검사할것이고, 어떤 이름인지 파라미터로 전달
        //우리가 테스트중이라서 BeanPropertyBindingResult 이 클래스를 직접 만들지만, 실제로는 spring MVC가 알아서 만들어서 매개변수를 전달해준다.
        //평소에는 전혀 사용할 일이 없다.

        event.setTitle("title");
        eventValidator.validate(event,errors);
        System.out.println(errors.hasErrors());
        //error가 있는지?

        errors.getAllErrors().forEach(e ->{
            System.out.println("==== error code ====");
            Arrays.stream(e.getCodes()).forEach(System.out::println);
            System.out.println(e.getDefaultMessage());
        });
    }
}

실행 결과

true
==== error code ====
notEmpty.event.title
notEmpty.title
notEmpty.java.lang.String
notEmpty
Empty title is not allowed.

title이 empty이기 때문에, errors.hasErrors(); 가 true를 return하고,

errors에다가 에러를 담아줄것이고,

에러 코드가 순차적으로 출력이 된다.

 

error code를 보면, 내가 만든 errorcode외에 notEmpty.event.title / notEmpty.title / notEmpty.java.lang.String이 세가지가 추가로 출력이 됐다.(Validator에서 알아서 추가해준것이다.)

 

ValidationUtils를 사용하지 않고 에러를 검증하는 방법 (잘 안쓰임)

Event event = (Event)target;
        if(event.getTitle() == null) {
            errors.rejectValue(String field, String errorcode);
        }

(특정필드에 대한 에러가 아니라)전반적인 객체의 에러일땐 ->  reject(String errorcode , String defaultMessage)

객체의 특정 필드에 대한 에러 -> rejectValue(String field, String errorcode , String defaultMessage)

 

최근에는 Validator를 사용하지않고, 스프링 부트를 사용한다면

기본적으로 우리가 구현한 Validator중에 LocalValidatorFactoryBean을 빈으로 자동등록해준다. -> spring이

아무런 빈을 등록을 안했지만, 아래와 같이 주입받아서 validate를 사용 할 수있다.

     

 EventValidator eventValidator = new EventValidator();
 eventValidator.validate(event,errors);

위에 코드는 사용하지 않는다.

@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    Validator validator;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(validator.getClass());

        Event event = new Event();
        event.setLimit(-1); //일부러 0이하 값을 넣는다.
        event.setEmail("a2232");//이메일이 아닌 값을 넣는다.
        //EventValidator eventValidator = new EventValidator();
        Errors errors = new BeanPropertyBindingResult(event, "event");
        //어떤 객체를 검사할것이고, 어떤 이름인지 파라미터로 전달
        //우리가 테스트중이라서 BeanPropertyBindingResult 이 클래스를 직접 만들지만, 실제로는 spring MVC가 알아서 만들어서 매개변수를 전달해준다.
        //평소에는 전혀 사용할 일이 없다.

        //eventValidator.validate(event,errors);
        validator.validate(event, errors);
        System.out.println(errors.hasErrors());
        //error가 있는지?

        errors.getAllErrors().forEach(e ->{
            System.out.println("==== error code ====");
            Arrays.stream(e.getCodes()).forEach(System.out::println);
            System.out.println(e.getDefaultMessage());
        });
    }
}
public class Event {
    Integer id;

    @NotEmpty
    String title;

    @NotNull @Min(0)
    Integer limit;

    @Email
    String email;
    
	//... getter and setter....
}

각각 필드에 annotation을 붙여줬다. 그리고 title은 값을 안넣었고, limit는 -1, email에는 이메일이 아닌값을 넣어줬다.

 

실행 결과

class org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
true
==== error code ====
NotEmpty.event.title
NotEmpty.title
NotEmpty.java.lang.String
NotEmpty
비어 있을 수 없습니다
==== error code ====
Min.event.limit
Min.limit
Min.java.lang.Integer
Min
0 이상이어야 합니다
==== error code ====
Email.event.email
Email.email
Email.java.lang.String
Email
올바른 형식의 이메일 주소여야 합니다

맨 윗줄을 보면, LocalValidatorFactoryBean이 자동으로 주입이 되었다.

결과는, title은 @NotEmpty 를 붙여줬기 때문에 오류가 발생하였고,

limit은 @min(0) , email은 @Email 어노테이션 때문에 오류가 발생하였다.

 

생각해보기

복잡하게 Validation 인터페이스를 구현해서 하는 것 보다 간단하게 1차적으로 @Valid 어노테이션으로 검증하고 2차적으로 비즈니스 로직에서 검증을 하는게 좋을 것 같다.

 

REFERENCE

  • https://www.baeldung.com/spring-boot-bean-validation#:~:text=When%20Spring%20Boot%20finds%20an,Boot%20throws%20a%20MethodArgumentNotValidException%20exception.
반응형
저작자표시 (새창열림)
  1. Validation은 크게 두가지가 있다.
  2. 특징
  3. 인터페이스 : org.springframework.validation.Validator
  4. 스프링 부트 2.0.5 이상 버전을 사용할 때
  5. Spring boot Validation
  6. ExceptionHanlder를 이용해 에러를 알려주자
  7. Spring validator 인터페이스 구현을 통한 validation
  8. 생각해보기
  9. REFERENCE
'💐 Spring/Spring 핵심 기술' 카테고리의 다른 글
  • 14. 데이터 바인딩 추상화 : Converter 와 Formatter
  • 13. 데이터 바인딩 추상화 : PropertyEditor
  • 11. Resource 추상화
  • 10. IoC 컨테이너 9부 : ResourceLoader
iseunghan
iseunghan
꾸준하게 열심히..
iseunghan
iseunghan

공지사항

  • 어제보다 나은 오늘이 되기 위해 🔥
  • 분류 전체보기 (262)
    • 💐 Spring (14)
      • 개념 및 이해 (2)
      • Spring 핵심 기술 (24)
      • Spring REST API (8)
      • Spring MVC, DB 접근 기술 (7)
      • Spring Security (23)
      • Spring in Action (1)
    • 🌻 JAVA (84)
      • 자바 ORM 표준 JPA 프로그래밍 (20)
      • 알고리즘, 자료구조 (13)
      • 디자인 패턴 (7)
      • 정리정리정리 (43)
      • JUnit (1)
    • 🔖 Snippets (3)
      • Javascript (3)
    • ⚙️ Devops (22)
      • ⛏ Git (11)
      • 🐳 Docker (6)
      • 🐧 Linux (3)
      • 🌈 Jenkins (1)
      • 📬 Kafka (1)
    • 💬 ETC.. (4)
      • 💻 macOS (2)
    • 🌧️ ORM (2)
      • JPA (2)
    • 🐍 Python (3)
    • 📚 Databases (15)
      • 오라클로 배우는 데이터베이스 개론과 실습(2판) (3)
      • RealMySQL 8.0 (8)
    • 🔥 Computer Science (5)
      • 📡 네트워크 (5)
    • 🏷️ 협업 (1)
    • 📜 코딩테스트 (38)
      • BAEKJOON\수학 1, 수학 2 (8)
      • BAEKJOON\재귀 (5)
      • BAEKJOON\브루트 포스 (3)
      • BAEKJOON\정렬 (1)
      • BAEKJOON\백트래킹 (5)
      • BAEKJOON\BFS, DFS (6)
      • BAEKJOON\이분탐색 (1)
      • BAEKJOON\다이나믹 프로그래밍 (9)
      • BAEKJOON\그리디 알고리즘 (0)
    • ✨ ISEUNGHAN (1)

인기 글

최근 글

전체
오늘
어제
반응형
hELLO · Designed By 정상우.
iseunghan
12. Validation 추상화
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.