검증로직 Validator를 만들어서 분리
복잡한 검증 로직을 별도로 분리하자.
컨트롤러에서 검증 로직이 차지하는 부분은 매우 크다.
@PostMapping("/add")
public String addItemV4(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
if (bindingResult.hasErrors()) {
log.info("erros = {}", bindingResult);
// log.info("model ={}", model);
return "validation/v2/addForm";
}
log.info("objectName={}", bindingResult.getObjectName());
log.info("target={}", bindingResult.getTarget());
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, 1000000}, null);
}
if (item.getQuantity() == null || item.getQuantity() >= 9999) {
bindingResult.rejectValue("quantity","max",new Object[]{9999}, null);
}
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin",new Object[]{10000, resultPrice}, null);
}
}
이런 경우 별도의 클래스로 역할을 분리하는 것이 좋다. 그리고 이렇게 분리한 검증 로직을 재사용 할 수도 있다
(item 를 사용하는 비슷한 컨트롤러가 있다면 재사용가능)
그리고 컨트롤러메소드가 깔끔해진다.
item객체 검증용 ItemValidator 생성
스프링은 검증을 체계적으로 제공하기 위해 Validator 인터페이스를 제공한다.
implements해서 구현해서 사용하면 된다.
@Component //스프링빈에 등록시켜서 컨트롤러에서 쉽게 가져다 쓰기위함
public class ItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Item.class.isAssignableFrom(clazz);
//매개변수로 들어오는 클래스타입과 item 클래스타입이 같은지 체크하는 부분
//매개변수로 들어오는 클래스타입이 item 클래스의 자식클래스인지 체크하는 부분
}
@Override
public void validate(Object target, Errors errors) { // target으로 검증할 객체가 넘어온다. Errors는 BindingResult의 부모클래스이다.
Item item = (Item) target; // Item 으로 캐스팅해서 사용
//컨트롤러에서 진행하던 검증부분을 그대로 가져왔다.
// Errors는 BindingResult의 부모클래스이므로 Errors에 BindingResult를 저장할 수있고
// errors안에도 reject,rejectValue가 존재하므로 그대로 메소드를 사용할 수 있다.
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);
}
}
}
}
itemAdd 메소드 V5
1. 스프링빈으로 등록되어있는 ItemValidator를 의존관계 주입한다. (생성자 주입)
2. 메소드 코드 내용
@PostMapping("/add")
public String addItemV5(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
itemValidator.validate(item, bindingResult); // validator 를 통해 검증을 진행한다. (bindingResult 객체를 전달해서 검증실패시 그 안에 에러들이 담기게한다)
if (bindingResult.hasErrors()) {
log.info("erros = {}", 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}";
}
원래는 아래코드처럼 class타입을 먼저 확인하고 검증메소드를 호출해야하긴하지만 바로 검증메소드를 호출해주었다.
if (itemValidator.supports(item.getClass())) { // validator 의 supports()를 이용해서 validator 에서 처리는 class가 맞는지 체크하고
itemValidator.validate(item, bindingResult); // validator 를 통해 검증을 진행한다. (bindingResult 객체를 전달해서 검증실패시 그 안에 에러들이 담기게한다)
}
실행해보면 기존과 완전히 동일하게 동작하는 것을 확인할 수 있다.
검증과 관련된 부분이 깔끔하게 분리되었다.
하지만 사실 스프링빈으로 등록하지않고(@Component 쓰지않고) Validator를 implement해서
사용하지도않는 supports()를 구현하지않고
그냥 validat()메소드를 만들어서
new ItemValidator()로 객체생성해서 가져온후 검증메소드를 사용해도 동작하긴한다.
(하지만 사용하는곳에서 매번 객체를 생성해줘야한다는 단점도 있긴함)
그렇다면 왜 굳이 Validator 인터페이스를 구현해서 사용하는것인가는
밑에서 알게된다.
Validator 분리2
스프링이 Validator 인터페이스를 별도로 제공하는 이유는 체계적으로 검증 기능을 도입하기 위해서다.
그런데 앞에서는 검증기를 직접 불러서 사용했고, 이렇게 사용해도 된다. ( Validator 인터페이스를 구현하고,
스프링빈으로 등록해서 주입받아 검증메소드를 사용하는 방식)
그런데 Validator 인터페이스를 사용해서 검증기를 만들면 스프링의 추가적인 도움을 받을 수 있다.
WebDataBinder를 통해서 사용하기
WebDataBinder 는 스프링의 파라미터 바인딩의 역할을 해주고 검증 기능도 내부에 포함한다
(@ModelAttribute를 붙이면 알아서 객체 바인딩해주는 그 파라미터 바인딩 기능과
validator를 넣어주면 검증기능 까지해주는 WebDataBinder를 스프링이 사용한다.)
WebDataBinder 코드 추가
@Slf4j
@Controller
@RequestMapping("/validation/v2/items")
@RequiredArgsConstructor
public class ValidationItemControllerV2 {
private final ItemRepository itemRepository;
private final ItemValidator itemValidator;
@InitBinder
public void init(WebDataBinder dataBinder) {
dataBinder.addValidators(itemValidator);
}
이렇게 WebDataBinder 에 검증기(Validator)를 추가하면
해당 컨트롤러에서는 검증기(Validator)를 자동으로 적용할 수 있다.
@InitBinder가 있는 해당 컨트롤러에만 영향을 준다. 글로벌 설정은 별도로 해야한다. (글로벌 설정은 마지막에 설명)
addItem 메소드 V6
@PostMapping("/add")
public String addItemV6(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
log.info("erros = {}", 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}";
}
@Validated라는 어노테이션을 추가되고 , 검증기의 검증메소드호출부분을 지웠다.
@Validated @ModelAttribute Item item
@Validated 동작방식
@Validated 는 검증기를 실행하라는 애노테이션이다.
이 애노테이션이 붙으면 앞서 WebDataBinder 에 등록한 검증기를 찾아서 실행한다.
그런데 여러 검증기를 등록한다면 그 중에 어떤 검증기가 실행되어야 할지 구분이 필요하다. 이때 supports() 가 사용된다.
여기서는 supports(Item.class) 호출되고, 결과가 true 이므로 ItemValidator 의 validate()가 호출된다.
validate()가 호출되면서 생기는 에러(필드에러,오브젝트에러)들은 알아서 BindingResult에 담긴다.
글로벌 설정 - 모든 컨트롤러에 다 적용 (참고용)
컨트롤러마다 @InitBinder를 이용해서 WebDataBinder에 Validator(검증기)를 등록하지않고
서버실행에 사용하는 application파일로 가서 WebMvcConfigurer를 implements하고 다음 코드를 추가하면
알아서 WebDataBinder에 Validator(검증기)가 등록된다.
@SpringBootApplication
public class ItemServiceApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(ItemServiceApplication.class, args);
}
@Override
public Validator getValidator() {
return new ItemValidator();
}
}
글로벌 설정을 하면 다음에 설명할 BeanValidator가 자동 등록되지 않는다. 글로벌 설정 부분은 주석처리 해두자.
참고로 글로벌 설정을 직접 사용하는 경우는 드물다!
@Validated , @Valid
@Validated @Valid 둘다 같은 애노테이션입니다.
@Valid: 자바 진영에서 공통으로 사용 가능
@Validated: 스프링 안에서만 사용가능
@Validated를 @Valid로 바꿔도 잘동작한다.
자세한건 다음 Bean Validation에
'인프런 > 스프링 MVC 2편' 카테고리의 다른 글
11) 로그인처리 - 쿠키, 세션 직접 개발,적용해보기(개념단계) (0) | 2023.02.06 |
---|---|
10) Bean Validation, @Validated(@Valid) ,프로젝트 (V3~V4) (0) | 2023.02.05 |
8)Validation(검증) , 오류 코드와 메시지처리 (4 ~ 6) (0) | 2023.02.03 |
7) Validation (검증) , 프로젝트 (V1 ~V2) ,오류 코드와 메시지처리 (1~3) (0) | 2023.02.03 |
6) 메시지 ,국제화 (0) | 2023.02.02 |
댓글