이 부분은 검증 로직에 관한 부분이다. 우리가 전에 만든 컨트롤러를 보면…
@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) {
}
}
@Override
public boolean supports(Class<?> clazz) {
return Item.class.isAssignableFrom(clazz);
}
이 친구는 인자로 주어진 객체가 해당 validator로 검증가능한 대상인지 판단해준다. 그냥 ==
을 if문 감싸서 만들수도 있지만, 상속관계까지 검증의 대상으로 삼기 위해 isAssignableFrom()
이라는 메서드를 이용함.
이 친구는 검증로직을 구현한다. 인자로 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);
}
}