Spring

Spring 검증 - Validator

녁이 2023. 12. 19. 17:08
728x90
반응형

2023.12.18 - [Spring] - Spring 검증 - BindingResult (FieldError, ObjectError, rejectValue, reject)

 

Spring 검증 - BindingResult (FieldError, ObjectError, rejectValue, reject)

검증 ( Client, Server 검증 ) 클라이언트 검증은 조작할 수 있으므로 보안에 취약 서버만으로 검증하면, 즉각적인 고객 사용성이 부족 둘을 섞어 사용하되, 최종적으로 서버 검증은 필수 API 방식 →

junhyuk-develop.tistory.com

해당 글은 위의 게시글에 이어 작성함을 참고하고 읽어주길 바람


Validator

public interface Validator {
boolean supports(Class<?> clazz);
void validate(Object target, Errors errors);
}

supports , validate 기능을 제공하는 인터페이스이다.

  • supports() {} : 해당 검증기를 지원하는 여부 확인(나중에 더 자세하게 설명)
  • validate(Object target, Errors errors) : 검증 대상 객체와 BindingResult(Errors 의 자식 클래스)

 

Validator를 상속하는 나만의 ItemValidator를 만들어보자. (컨트롤러에 있는 검증 로직을 분리하기 위함)


 

ItemValidator

@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) {
 	Item item = (Item) target;
 	ValidationUtils.rejectIfEmptyOrWhitespace(errors, "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() > 10000) {
 		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);
            }
 		}
 	}
}

@Component을 통해 스프링이 컴포넌트 스캔을 통해 사용할 수 있도록 설정해준다.

또한, supports를 통해 ItemValidator를 적용하는 객체가 item 객체인지 아닌지 확인한다.

( isAssignableFrom을 사용하면 자식 클래스까지 확인 가능 )

그리고, validate를 통해 검증 로직을 실행한다.

 


WebDataBinder

위에서 만든 ItemValidator를 컨트롤러에서 사용하려면 WebDataBinder를 사용하면 된다.

WebDataBinder 는 스프링의 파라미터 바인딩의 역할을 해주고 검증 기능도 내부에 포함한 isAssignableFrom 다.

 

컨트롤러에 추가해보자.

@InitBinder
public void init(WebDataBinder dataBinder) {
 log.info("init binder {}", dataBinder);
 dataBinder.addValidators(itemValidator);
}

이렇게 WebDataBinder에 검증기를 추가하면 해당 컨트롤러에서는 검증기를 자동으로 적용할 수 있다.

@InitBinder → 해당 컨트롤러에만 영향을 준다. 글로벌 설정은 별도로 해야한다.

 

이제 검증기를 사용할 부분에서 @Validated 를 통해 검증기를 적용해보자.

@PostMapping("/add")
public String addItemV6(@Validated @ModelAttribute Item item, BindingResult 
bindingResult, RedirectAttributes redirectAttributes) {
 
 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}";
}

 

 

@Validated 는 검증기를 실행하라는 에노테이션이다.

이 애노테이션이 붙으면 앞서 WebDataBinder 에 등록한 검증기를 찾아서 실행한다.

그런데 여러 검증기를 등록한다면 그 중에 어떤 검증기가 실행되어야 할지 구분이 필요하다.

이때 supports() 가 사용된다.

→ supports(Item.class) 호출되고, 결과가 true 이므로 ItemValidator 의 validate() 가 호출된다.

 


참고

검증시 @Validated @Valid 둘다 사용가능

@Valid 를 사용하려면 build.gradle 의존관계 추가가 필요하다.

 implementation 'org.springframework.boot:spring-boot-starter-validation'

@Valid는 java 표준 검증 에노테이션이고, @Validated는 자바 스프링이 제공하는 검증 에노테이션이다.


정리

컨트롤러의 부담을 줄이고 검증 로직을 따로 Validator를 통해 분리했다.

WebDataBinder를 통해 컨트롤러에 검증기를 추가했고, @Validated 를 통해 검증기를 적용시켰다.

다음 게시글에서는 BeanValidator에 대해서 설명할 것이다.

이는 스프링이 제공하는 편리한 기능이니 기대해도 좋다.

728x90
반응형