728x90
Controller에 Validation 분리
- Validation을 Controller에 모여 있어 실제 Controller 로직을 기능을 찾기 힘들다.
- Validation 로직을 실제 새로운 Class를 만들어 분리시켜 동작시켜 보자
ItemValication.class
@Component
public class ItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
//item == clazz
return Item.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Item item = (Item) target;
//ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "itemName", "required");
//검증 로직
if (!StringUtils.hasText(item.getItemName())) {
//objectName 같은 경우 bindingResult 가 이미 알고있어서 쓰지 않아도 된다.
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);
}
}
}
}
addItemV5
@PostMapping("/add")
public String addItemV5(@ModelAttribute Item item, BindingResult bindingResult,
RedirectAttributes redirectAttributes, Model model) {
itemValicator.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}";
}
- supports() {} : 해당 검증 기를 지원하는 여부 확인(뒤에서 설명)
- validate(Object target, Errors errors) : 검증 대상 객체와 BindingResult
- ItemValidator를 스프링 빈으로 주입받아서 직접 호출했다.
WebDataBinder를 통해 사용하기
private final ItemValidator itemValidator;
@InitBinder
public void init(WebDataBinder dataBinder){
dataBinder.addValidators(itemValidator);
}
@PostMapping("/add")
public String addItemV6(@Validated @ModelAttribute Item item, BindingResult bindingResult,
RedirectAttributes redirectAttributes, Model model) {
//검증에 실패하면 다시 입력 폼으로
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를 직접 호출하는 부분이 사라지고, 대신에 검증 대상 앞에 @Validated 가 붙었다.
- @Validated는 검증 기를 실행하라는 애노테이션이다.
- 이 애노테이션이 붙으면 앞서 WebDataBinder에 등록한 검증 기를 찾아서 실행한다. 그런데 여러 검증 기를 등록한다면 그중에 어떤 검증기가 실행되어야 할지 구분이 필요하다. 이때 supports()가 사용된다
- 글로벌 설정 - 모든 컨트롤러에 다 적용
@SpringBootApplication
public class ItemServiceApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(ItemServiceApplication.class, args);
}
@Override
public Validator getValidator() {
return new ItemValidator();
}
}
- 기존 컨트롤러의 @InitBinder 를 제거해도 글로벌 설정으로 정상 동작하는 것을 확인할 수 있다
- 글로벌 설정을 하면 다음에 설명할 BeanValidator가 자동 등록되지 않는다. 글로벌 설정 부분은 주석처리해두자. 참고로 글로벌 설정을 직접 사용하는 경우는 드물다.
- 참고
- 검증시 @Validated @Valid 둘 다 사용 가능하다.
- javax.validation.@Valid 를 사용하려면 build.gradle 의존관계 추가가 필요하다.
- implementation 'org.springframework.boot:spring-boot-starter-validation'
- @Validated 는 스프링 전용 검증 애노테이션이고, @Valid는 자바 표준 검증 애노테이션이다.
728x90
'스프링 MVC 2편(백엔드 웹 개발 활용 기술)' 카테고리의 다른 글
| Ch05. 검증(Bean Validation) - Bean Validation(스프링 적용) (0) | 2022.03.16 |
|---|---|
| Ch05. 검증(Bean Validation) - Bean Validation(시작) (0) | 2022.03.16 |
| Ch04. 검증(Validation) - 오류 코드와 메시지 처리 (0) | 2022.03.15 |
| Ch04. 검증(Validation) - FliedError, ObjectError (0) | 2022.03.15 |
| Ch04. 검증(Validation) - BindingResult (0) | 2022.03.14 |