250x250
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 다형성
- HttpServletResponse
- @configuration
- 스프링
- 코드트리
- DI
- 스프링컨테이너
- 코드트리조별과제
- html form
- 백준
- 싱글톤
- 오블완
- 테스트코드
- 참조변수
- 티스토리챌린지
- 코딩테스트
- fielderror
- 인터페이스
- 추상클래스
- 김영한
- 서블릿
- java
- 프록시
- 오버라이딩
- 의존관계
- equals()
- objecterror
- JSON
- http 메시지 컨버터
- ocp
Archives
- Today
- Total
minOS
Validator 분리 본문
728x90
Validator 분리1
목표 :복잡한 검증 로직을 별도로 분리
컨트롤러에서 검증 로직이 차지하는 부분은 매우 크다. 이런 경우 별도의 클래스로 역할을 분리하는 것이 좋다. 그리고 이렇게 분리한 검증 로직을 재사용할 수도 있다.
스프링이 제공하는 Validator 인터페이스를 구현하려고 한다.
public interface Validator { /** * Can this {@link Validator} {@link #validate(Object, Errors) validate} * instances of the supplied {@code clazz}? * <p>This method is <i>typically</i> implemented like so: * <pre class="code">return Foo.class.isAssignableFrom(clazz);</pre> * (Where {@code Foo} is the class (or superclass) of the actual * object instance that is to be {@link #validate(Object, Errors) validated}.) * @param clazz the {@link Class} that this {@link Validator} is * being asked if it can {@link #validate(Object, Errors) validate} * @return {@code true} if this {@link Validator} can indeed * {@link #validate(Object, Errors) validate} instances of the * supplied {@code clazz} */ boolean supports(Class<?> clazz); /** * Validate the supplied {@code target} object, which must be * of a {@link Class} for which the {@link #supports(Class)} method * typically has (or would) return {@code true}. * <p>The supplied {@link Errors errors} instance can be used to report * any resulting validation errors. * @param target the object that is to be validated * @param errors contextual state about the validation process * @see ValidationUtils */ void validate(Object target, Errors errors); }
구현체 ItemValidator를 만들었다.
public class ItemValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return Item.class.isAssignableFrom(clazz); // Item == clazz // Item == 자손 Item 도 검증가능 } @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,10000},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); } } } }
ItemValidator 를 @Componet을 통해 스프링 빈으로 등록한다.
@Component public class ItemValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return Item.class.isAssignableFrom(clazz); // Item == clazz // Item == 자손 Item 도 검증가능 } @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,10000},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); } } } }
controller에 생성자 기반 의존성 주입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) { itemValidator.validate(item,bindingResult); //검증하기 // 검증에 실패하면 다른 입력 폼으로 if(bindingResult.hasErrors()){ log.info("error ={}", bindingResult); //model.addAttribute("errors",errors); BindResult는 자동으로 view로 넘어감 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 분리2
스프링이 `Validator` 인터페이스를 별도로 제공하는 이유는 체계적으로 검증 기능을 도입하기 위해서다. 그런데 앞에서는 검증기를 직접 불러서 사용했고, 이렇게 사용해도 된다. 그런데 `Validator` 인터페이스를 사용해서 검증기를 만들면 스프링의 추가적인 도움을 받을 수 있다.
WebDataBinder를 통해서 사용하기
컨트롤러에 아래의 코드 추가
@InitBinder public void init(WebDataBinder dataBinder) { log.info("init binder {}", dataBinder); dataBinder.addValidators(itemValidator); }
이렇게 `WebDataBinder` 에 검증기를 추가하면 해당 컨트롤러에서는 검증기를 자동으로 적용할 수 있다. `@InitBinder` 해당 컨트롤러에만 영향을 준다. 글로벌 설정은 별도로 해야한다.
validator를 직접 호출하는 부분이 사라지고, 대신에 검증 대상 앞에 `@Validated가 붙었다.
@PostMapping("/add") public String addItemV6(@Validated @ModelAttribute Item item, BindingResult bindingResult , RedirectAttributes redirectAttributes, Model model) { // 검증에 실패하면 다른 입력 폼으로 if(bindingResult.hasErrors()){ log.info("error ={}", bindingResult); //model.addAttribute("errors",errors); BindResult는 자동으로 view로 넘어감 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는 검증기를 실행하라는 애노테이션이다. 이 애노테이션이 붙으면 앞서 `WebDataBinder` 에 등록한 검증기를 찾아서 실행한다. 그런데 여러 검증기를 등록한다면 그 중에 어떤 검증기가 실행되어야 할지 구분이 필요하다. 이때 `supports()` 가 사용된다. 여기서는 supports(Item.class)` 호출되고, 결과가 `true` 이므로 `ItemValidator` 의 `validate()` 가 호출된다.
@Component public class ItemValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return Item.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) {...} }
글로벌 설정 - 모든 컨트롤러에 다 적용
@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가 자동 등록되지 않는다
728x90
'TIL > 김영한의 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술' 카테고리의 다른 글
Bean Validation - 오브젝트 오류,수정에 적용,한계 (0) | 2024.09.18 |
---|---|
Bean Validation - Bean Validation 애노테이션 적용,스프링 적용,에러 코드 (0) | 2024.09.18 |
오류 코드와 메세지 처리 (7) | 2024.09.13 |
Validation - FieldError, ObjectError (4) | 2024.09.11 |
Validation - BindingResult (0) | 2024.09.11 |