Spring/Spring MVC

스프링 MVC - Validator 분리

taey 2024. 10. 1. 18:06

Validator 분리

 

스프링은 검증을 체계적으로 제공하기 위해 다음 인터페이스 제공한다.

public interface Validator {
    boolean supports(Class<?> clazz);
    void validate(Object target, Errors errors);
}
  • supports() {} : 해당 검증기를 지원하는 여부 확인(뒤에서 설명)
  • validate(Object target, Errors errors): 검증 대상 객체와 BindingResult

예시 코드

package hello.itemservice.web.validation;

import hello.itemservice.domain.item.Item;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

@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);
     		}
     	}
     }
}

 

 


WebDataBinder

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

 

컨트롤러 코드 안에 @InitBinder를 추가 (Validator 직접 추가)

@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}";
}

 

Validator를 직접 호출하지 않고, 검증 대상 앞에 @Validated가 붙었다.

 

동작 방식 

@Validated는 검증기를 실행하라는 애노테이션이다. 이 애노테이션이 붙으면 앞서 WebDataBinder에 등록한 검증기를 찾아서 실행한다. 그런데 여러 검증기를 등록한다면 그 중에 어떤 검증기가 실행되어야 할 지 구분이 필요하다. 이때 supports()가 사용된다. 여기서는 supports(Item.class)가 호출되고, 결과가 true이므로 ItemValidator의 validate()가 호출된다.

 

 

글로벌 설정 - 모든 컨트롤러에 적용

@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 둘 다 사용가능하다.
javax.validation.@Valid를 사용하려면 build.gradle 의존관계 추가가 필요하다.
implementation 'org.springframework.boot:spring-boot-starter-validation' @Validated 는 스프링 전용 검증 애노테이션이고, @Valid 는 자바 표준 검증 애노테이션이다.