Spring MVC/Validation

Validation 검증 - Validator 이해와 MyValidator 만들어보기

Hcode 2022. 12. 4.

 

이전 글로 FieldError를 사용하여, 사용자 입력 보존과 오류 메시지 남기기를 알아보았습니다.
 

Validation 검증 - 사용자 입력 보존과 오류 메시지 남기기

이전 글로 Validation 오류를 담는 객체인 BindingResult 객체에 대한 사용을 알아보았습니다. Validation 검증 - BindingResult 사용 BindingResult 사용 BindingResult가 사용되는 위치 FieldError ObjectError BindingResult에

hhhhicode.tistory.com

이번 글로 개발자가 직접 특정 필드에 대한 오류와 글로벌 오류를 작성하지만, Validation 로직이 Controller와 분리되는 방법인 Validator를 알아보겠습니다.

 


 

Validator

  • Validator 사용 목적
  • Validator 살펴보기
  • MyValidator 만들어보기

를 다룹니다.

 


 

Validator 사용 목적

  • 복잡한 Validation 로직을 별도로 분리합니다.
  • 분리되는 Validation 로직은, Validator Interface를 상속받아서 기능을 체계화합니다.
  • Validator Interface를 상속받아서 사용하면, 스프링이 자동화해주는 부분이 있습니다.

 

Controller에서 Validation을 분리하지 않으면, Validation 로직이 차지하는 부분은 매우 큽니다.

이런 경우 별도의 클래스로 역할을 분리하는 것이 좋습니다.

이렇게 분리된 검증 로직은 재사용할 수도 있습니다.

 


 

Validator 살펴보기

스프링은 체계적인 Validation을 제공하기 위해 Validator Interface를 제공합니다.

Validator Interface를 살펴보겠습니다.

 


 

Validator Interface

public interface Validator {

	/**
	 * Can this {@link Validator} {@link #validate(Object, Errors) validate}
	 * instances of the supplied {@code clazz}?
	 * <p>This method is <i>typically</i> implemented like so:
	 * <pre class="code">return Foo.class.isAssignableFrom(clazz);</pre>
	 * (Where {@code Foo} is the class (or superclass) of the actual
	 * object instance that is to be {@link #validate(Object, Errors) validated}.)
	 * @param clazz the {@link Class} that this {@link Validator} is
	 * being asked if it can {@link #validate(Object, Errors) validate}
	 * @return {@code true} if this {@link Validator} can indeed
	 * {@link #validate(Object, Errors) validate} instances of the
	 * supplied {@code clazz}
	 */
	boolean supports(Class<?> clazz);

	/**
	 * Validate the supplied {@code target} object, which must be
	 * of a {@link Class} for which the {@link #supports(Class)} method
	 * typically has (or would) return {@code true}.
	 * <p>The supplied {@link Errors errors} instance can be used to report
	 * any resulting validation errors.
	 * @param target the object that is to be validated
	 * @param errors contextual state about the validation process
	 * @see ValidationUtils
	 */
	void validate(Object target, Errors errors);

}

 

supports()

해당 Validator를 지원하는지 여부를 확인합니다.

주석을 보시면 일반적으로

return Foo.class.isAssignableFrom(clazz);

해당 방식으로 사용한다고 합니다.

Foo는 검증되는 인스턴스의 클래스이거나 상위 클래스입니다.

 

supports에 대해서는 자식 클래스도 생각해 보아야 합니다.

Object1을 지원한다면, Object1을 상속받는 Object1_1도 지원되어야 합니다.

Object1_1은 Object1을 포함하고 있기 때문에 당연하다고 생각합니다.

예를 들자면,
  사람은 언젠가 죽습니다. 남자는 사람입니다. 남자는 언젠가 죽습니다.

 


 

validate(Object target, Error errors)

Validation 대상 객체와 BindingResult입니다. 

Validation 로직을 작성합니다.

BindingResult는 Error를 상속받고 있습니다.
BindingResult가 더 많은 기능을 제공하기도 하고 관례상 많이 사용되고 있습니다.

 


 

MyValidator 만들어보기

Validator Interface를 상속받는 MyValidator를 만들어보겠습니다.

public class MyValidator implements Validator {

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

    @Override
    public void validate(Object target, Errors errors) {
        MemberAddDto targetObject = (MemberAddDto) target;

        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userId", "required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "emailAddress", "required");

        if (1000 < targetObject.getMemo().length()) {
            errors.rejectValue("memo", "max", new Object[]{1000}, null);
        }
    }
}

 

supports()

  • MemberAddDto.class.isAssignableFrom(clazz)

자식 클래스까지 커버가 되는 isAssignableFrom을 사용합니다.

Validator Interface의 주석을 보아도 일반적으로 이렇게 사용된다고 합니다.

MemberAddDto 클래스는 파라미터 전달된 clazz와 동일하거나 상위 클래스인지 따져보는 조건문입니다.

MemberAddDto는 제가 개인 프로젝트에서 사용 중인 Dto 객체의 이름입니다.

 


 

validate(Object target, Error errors)

  • ValidationUtils는 Validation의 간편함을 주는 몇몇 간단한 메서드를 제공합니다.
  • rejectValue(...) : 특정 필드에 대한 거절 메시지를 설정합니다. FieldError가 추가됩니다.
  • reject(...) : 특정 필드를 넘어서는 글로벌 에러에 대한 거절 메시지를 설정합니다. ObjectError가 추가됩니다.

본문에는 글로벌 에러에 대한 내용이 없기 때문에 예시를 보여드립니다.

int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
	errors.reject("totlaPriceMin", new Object[]{10000, resultPrice}, null);
}

 


 

다음 글로 Validator를 사용하는 2가지 방법에 대해 알아보겠습니다.
 

Validation 검증 - Validator를 사용하는 2가지 방법

이전 글로 Validator로 분리하여 Controller의 로직과 Validation을 분리하는 방법을 알았고 MyValidator도 만들어 보았습니다. Validation 검증 - Validator 이해와 MyValidator 만들어보기 이전 글로 FieldError를 사용

hhhhicode.tistory.com

 

댓글