웹 사이트에서 Validation으로 데이터를 검증하는 작업은 중요한 작업 입니다 하지만 검증자체가 코드가 짧지 않기 때문에 모든 컨트롤러에 직접 작성하게 되면 전체코드가 길어지게 되고 실행 컨트롤러 작업코드를 찾기 어려울 것입니다. 그래서 Validaion코드는 따로 java파일로 만들어 분리해주는게 좋습니다.
@Component
public class ItemValidation implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return Item.class.isAssignableFrom(aClass);
}
@Override
public void validate(Object o, Errors errors) {
Item item = (Item) o;
//검증 로직
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,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);
}
}
}
}
해당 컨트롤러와 같은 위치에 java파일을 만들고 Validator 인터페이스를 상속받습니다. 그리고 편하게 빈으로 사용하기 위해 @Component를 명시하였습니다. Validator 인터페이스를 상속받게 되면 두개의 메소드가 오버라이딩 되는데 첫번째 supports는 검증할 target의 클래스를 확인하는 메소드인데 사용처는 뒤에 설명드리겠습니다. 두번째 메소드는 실제 검증코드가 들어갈 메소드입니다. 인자로는 object와 error가 들어오는데 target과 bindingResult가 들어오게 됩니다.
private final ItemValidation itemValidation;
@PostMapping("/add")
public String addItemV5(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes , Model model) {
itemValidation.validate(item,bindingResult);
//검증에 실패하면 다시 입력 폼으로
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 인터페이스를 상속받지 않고 컨트롤러에서 new로 객체를 만들어 메소드를 실행해도 작동은 정상적으로 될 것입니다. 하지만 굳이굳이 Validator 인터페이스를 상속받은 이유는 해당 빈을 컨트롤러에서 명시하여 호출하지 않고도 검증이 가능하기 때문입니다. 방법으로는 WebDataBinder를 만들고 그 안에 검증파일들을 넣어준 뒤 컨트롤러에서 ModelAttirbute앞에 @Validated를 붙여주면 됩니다.
@InitBinder
public void init(WebDataBinder dataBinder) {
dataBinder.addValidators(itemValidator);
}
컨트롤러 상단에 위의 코드를 넣고 addValidators에 빈에 등록한 검증파일들을 넣어준 뒤
public String addItemV6(@Validated @ModelAttribute Item item, BindingResult
bindingResult, RedirectAttributes redirectAttributes)
검증이 이루어질 컨트롤러 ModelAttribute앞에 @Validated를 붙여주면 끝입니다.
그런데 여기서 궁금한 점은 dataBinder에는 여러 종류의 target이 들어올텐데 각 컨트롤러에 맞는 검증이 어떻게 이루어질지 의문입니다.
그 의문에 정답은 분리된 검증소스 파일이 Validator 인터페이스를 상속받았을때 오버라이딩한
@Override
public boolean supports(Class<?> aClass) {
return Item.class.isAssignableFrom(aClass);
}
해당 코드 덕분입니다. 모든 검증파일에서 target의 객체타입을 비교하여 일치하는 검증파일이 실행되기 때문입니다.
.isAssignableFrom은 받아온 target의 객체뿐만 아니라 객체의 자식들까지도 모두 true로 만들어주는 착한 친구입니다.
'spring' 카테고리의 다른 글
Bean Validation (0) | 2024.05.18 |
---|---|
Spring Boot 메세지, 국제화 (0) | 2024.05.16 |
Validation V5 (0) | 2024.05.15 |
Validation V4 (1) | 2024.05.14 |
Validation V3 (0) | 2024.05.14 |