Validator분리(1)

학습 페이지

이 부분은 검증 로직에 관한 부분이다. 우리가 전에 만든 컨트롤러를 보면…

@PostMapping("/add")
    public String addItemV4(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {

        log.info("objectName={}", bindingResult.getObjectName());
        log.info("objectTarget={}", bindingResult.getTarget());

        //타입검증에 실패하면 다시 입력 폼으로 이동
        if(bindingResult.hasErrors()){
            log.info("errors = {}", bindingResult);
            return "validation/v2/addForm";
        }

        //검증 로직
        if(!StringUtils.hasText(item.getItemName())){
            bindingResult.rejectValue("itemName", "required");

        }

        if(item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000){
            bindingResult.rejectValue("price", "range", new Object[]{1000, 10000000}, null);
        }

        if(item.getQuantity() == null || item.getQuantity() >= 9999){
            bindingResult.rejectValue("quantity", "max", new Object[]{9999}, null);
        }

        //특정 필드가 아니라 복합 룰 검증
        if(item.getQuantity() != null && item.getPrice() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if(resultPrice < 10000){
                bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }

        //검증에 실패하면 다시 입력 폼으로 이동
        if(bindingResult.hasErrors()){
            log.info("errors = {}", bindingResult);
            return "validation/v2/addForm";
        }

        //검증에 성공했을때의 로직

        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/validation/v2/items/{itemId}";
    }

실제 검증에 성공했을때 돌릴 로직보다, 검증로직자체가 너무 길다. 핸들러 본연의 기능이 뭔지 찾아보기 어렵다….

그래서 이제 Validator라고, 검증하는 부분에 이름을 붙이고 컨트롤러와 분리해줄 것이다… Validator를 만들어주자.

package hello.itemservice.web.validation;

import org.springframework.validation.Validator;

public class ItemValidator implements Validator {
}

그리고 이것도 그냥 쌩으로 만드는게 아니라 스프링이 지원하는 Validator를 상속받도록 만들자.

이 Validator 인터페이스는 메서드를 두개 지원한다. support와 validate…

package hello.itemservice.web.validation;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

public class ItemValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return false;
    }

    @Override
    public void validate(Object target, Errors errors) {

    }
}

Supports

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

이 친구는 인자로 주어진 객체가 해당 validator로 검증가능한 대상인지 판단해준다. 그냥 == 을 if문 감싸서 만들수도 있지만, 상속관계까지 검증의 대상으로 삼기 위해 isAssignableFrom()이라는 메서드를 이용함.

validate

이 친구는 검증로직을 구현한다. 인자로 target과 errors를 받는다. target은 검증 대상이 되는 객체, errors는 이전에 BindingReulst가 상속받는다는 그 인터페이스이다.

  @Override
    public void validate(Object target, Errors errors) {
        Item item = (Item) target;

        //검증 로직
        if(!StringUtils.hasText(item.getItemName())){
            errors.rejectValue("itemName", "required");

        }

        if(item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000){
            errors.rejectValue("price", "range", new Object[]{1000, 10000000}, null);
        }

        if(item.getQuantity() == null || item.getQuantity() >= 9999){
            errors.rejectValue("quantity", "max", new Object[]{9999}, null);
        }

        //특정 필드가 아니라 복합 룰 검증
        if(item.getQuantity() != null && item.getPrice() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if(resultPrice < 10000){
                errors.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }