티스토리 뷰
Validator란?
스프링에서 사용자가 입력한 값에 대해서 올바른지에 대한 검증기를 제공한다. 바로 Validator 인터페이스이다.
상품의 가격을 입력해야 하는데 숫자가 아닌 문자를 입력했거나 제한된 값을 넘어서거나 등등 이에 대응할 수 있게 해준다.
Validation 인터페이스는 다음과 같은 메서드를 제공한다.
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와 validate 메서드를 제공하는데 각각의 역할을 다음과 같다.
supports : 검증하려는 클래스가 검증기에 등록된 클래스들 중에 존재하는지 확인한다.
validate : 해당 클래스의 값에 대하여 검증한다.
Validator 사용방법
현재 사용자가 입력하려고 하는 화면은 다음과 같다. Item Name, price, quantity 필드가 존재하는데 각 필드별 값들은 Item 객체에 저장된다.
당연하게도 price와 quantity에는 문자가 아닌 숫자를 입력해야 한다. 사용자가 문자를 입력했을 때 서버측에서 이에 대한 대비를 해야한다. validator를 쓰지 않고 상품을 등록하는 메서드를 처리하는 Controller에 바로 if문을 작성하여 문자 입력을 방지할 수도 있다. 그러나 컨트롤러의 역할과 검증기의 역할을 분리하는 것이 코드 작성하는 것에 있어서 효율적이다.
따라서 Validator를 사용해야 하는 것이다.
이제 실제로 사용하는 코드를 보자.
public class ItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Item.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Item item = (Item) target;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "itemName", "required");
if(item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
errors.rejectValue("price", "range", new Object[]{1000, 1000000}, null);
}
if(item.getQuantity() == null || item.getQuantity() > 9999) {
errors.rejectValue("quantity", "max", new Object[]{9999}, null);
}
if(item.getPrice() != null && item.getQuantity() != null) {
int result = item.getPrice() * item.getQuantity();
if(result < 10000) {
errors.reject("min", new Object[]{10000}, null);
}
}
}
}
supports를 보면 isAssignableFrom을 사용하여 상속관계 또는 구현관계를 확인하는 것인데 사용법은 아래와 같고 추가적인 설명은 하지 않겠다.
supports에서 true를 반환하면 validate를 사용할 수 있다. validate의 매개변수 중에 object에는 검증할 값을 가지고 있는 객체를 가지고 있고 errors에는 오류값을 저장하고 있는 BindingResult가 저장되어 있다.
이제 Controller를 보자.
아래 메서드는 검증기를 해당 Controller에만 등록하는 것이다. 검증기를 등록하지 않고 직접 메서드 내부에 검증하는 코드를 작성할 수도 있다.
@InitBinder
void init(WebDataBinder binder) {
log.info("init binder = {}", binder);
binder.addValidators(validator);
}
검증기를 등록한 덕분에 애너테이션으로 검증을 수행할 수 있다. 검증을 수행하는 애너테이션은 @Validated이다.
Item 객체에 애너테이션을 붙였으므로 해당 객체에 검증을 수행한다는 의미이다. 위에서 supports를 구현할 때 return Item.class.isAssignableFrom(Item.class)로 작성했었다. 그러므로 item 객체의 검증을 정상적으로 수행한다.
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult,
RedirectAttributes redirectAttributes, Model model) {
// 코드 내부 생략
}
결론
스프링에서 제공하는 Validator를 알아보았다. 글에서 설명했듯이 그냥 컨트롤러 내부에 if문을 여러 개 작성해서 잘못된 값을 방지할 수도 있지만 이는 컨트롤러에 너무 많은 역할을 부여하므로 비효율적이다. 우리는 항상 서로 다른 역할을 수행하는 코드는 분리해서 작성할 필요가 있다.
마지막에 @InitBinder를 사용하여 해당 컨트롤러에만 검증기를 등록하였다. 이 방법 말고 아래와 같이 직접 메서드 내부에 검증코드를 작성할 수 있다.
@Controller
@RequiredArgsConstructor
public class ValidationItemControllerV2 {
private final ItemValidator validator;
// 코드 생략
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
validator.validate(item, bindingResult); // 직접 등록
}
// 코드 생략
}