위와 같이 컨트롤러에서 검증 로직이 차지하는 부분이 매우 크다. 이런 경우네는 별도의 클래스로 역할을 분리하는 것이 좋다.
또한 클래스로 분류하게 되면 재사용성에도 용이해진다.
@Component
public class ItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Item.class.isAssignableFrom(clazz);
// item == clazz
// item == subItem
}
해당 클래스가 Item 클래스 인지 아니면 Item의 자식 클래스인지 확인해주는 supprots 메소드
= Validator가 지원하는 Class 인지 확인하는 Method
@Override
public void validate(Object targetm Errors errors) {
// Object는 item
// Errors 는 bindingResult 의 부모 클래스
Item item = (Item) target;
//검증 로직
if (!(StringUtils.hasText(item.getItemName()))) {
errors.rejectValue("itemName", "required");
}
if (item.getPrice() == null || item.getPrice() < 100 || 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 resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
errors.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
}
검증 하는 Method에는 컨트롤러에서 구현했던 검증 로직을 그대로 쓰면 된다.
이때 Errors는 bindingResult의 부모 클래스 이므로
bindingResult.rejectValue() -> errors.rejectValue()로 바꿔도 괜찮다.
@Controller
@RequiredArgsConstructor
public class ValidationItemControllerV2 {
private final ItemRepository itemRepository;
private final ItemValidator itemValidator;
@PostMapping("/add")
public String addItemV5(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
//Validator로 검증
itemValidator.validate(item, bindingResult);
// 검증에 실패하면 다시 입력 폼으로
if (bindingResult.hasErrors()) {
log.info("bindingResult = {}", 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를 스프링 Bean으로 주입 받아서 직접 호출하기만 하면 기존과 동일하게 작동한다.
참고 : 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술