BindingResult, FieldError, ObjectError를 통해 Controller에서 값을 검증할 수 있지만, 이 경우 Controller의 크기가 너무 커지고 단일 책임 원칙에 위배된다.
그래서 Spring에서는 Validator라는 인터페이스를 통해 별도의 검증용 Spring Bean을 만들 수 있도록 기능을 제공한다.
프로그래머는 Validator 인터페이스를 상속받아 구현체를 만들어 검증 로직을 구현할 수 있다. 그리고 Spring Bean으로 만들어지기 때문에, Controller에서는 해당 구현체를 주입받아 사용할 수 있게 된다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
Validator를 사용하기 위해선 위처럼 build.gradle
에 등록해야한다.
package org.springframework.validation;
public interface Validator {
boolean supports(Class<?> clazz);
void validate(Object target, Errors errors);
}
boolean supports()
return Domain.class.isAssignableFrom(clazz);
void validate()
Object target
: 검증 대상 객체Errors errors
: BindingResultpackage org.springframework.validation;
public interface Errors {
String NESTED_PATH_SEPARATOR = PropertyAccessor.NESTED_PROPERTY_SEPARATOR;
void reject(String errorCode);
void reject(String errorCode, String defaultMessage);
void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);
void rejectValue(@Nullable String field, String errorCode);
void rejectValue(@Nullable String field, String errorCode, String defaultMessage);
void rejectValue(@Nullable String field, String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage);
}
String errorCode
errors.properties
에 저장된 Key 값을 찾는 용도로 사용된다.@Nullable Object[] errorArgs
@Nullable String defaultMessage
@Nullable String field
Validator 인터페이스를 상속받아 구현체를 작성하였다면, Controller에 주입시켜 사용할 수 있는데, 이는 예제를 통해 알아보자.
@Getter
@RequiredArgsConstructor
public class Item {
private final String itemName;
private final Integer price;
private final Integer quantity;
}
단순히 값 검사와 클라이언트의 요청을 받기 위한 데이터 객체이다.
@Getter
@RequiredArgsConstructor
final
키워드가 붙은 모든 field에 대한 생성자 생성@Component
public class ItemValidator implements Validator {
/**
* Item 타입과 clazz 타입이 같은지 검사
*/
@Override
public boolean supports(Class<?> clazz) {
return Item.class.isAssignableFrom(clazz);
}
/**
* 검증 로직
*
* @param target 유효성을 검사할 객체
* @param errors BindingResult 의 부모 객체
*/
@Override
public void validate(
Object target, Errors errors
) {
Item item = (Item) target;
// 검증 로직
// itemName != null && itemName != " "
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() >= 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);
}
}
}
}
boolean supports()
return Item.class.isAssignableFrom(clazz);
public void validate(Object *target*, Errors *errors*)
””
, “ “
, null
)@RestController
public class ValidationController {
private final ItemValidator itemValidator;
@Autowired
public ValidationController(
ItemValidator itemValidator
) {
this.itemValidator = itemValidator;
}
@InitBinder
public void init(
WebDataBinder dataBinder
) {
dataBinder.addValidators(itemValidator);
}
}
WebDataBinder.addValidators
@InitBinder
@InitBinder("key")
@RequestMapping
에서 지원하는 Arguments를 모두 지원한다.@RestController
public class ValidationController {
// ...
@PostMapping("/valid")
public String valid(
@Validated @RequestBody Item item,
BindingResult bindingResult
) throws Exception {
if (bindingResult.hasErrors()) {
throw new ValidationException("validation failed");
}
return "validation ok";
}
}
@Validated
org.springframework.validation.annotation
@Validated
를 지원하기 전에는 Java에서 제공하는 @Valid
를 사용했다.@Validated
애노테이션을 붙이면 된다.클라이언트의 요청이 오면 다음의 순서를 따른다.
ValidationException("validation failed");
발생"validation ok"
를 클라이언트에게 전달.검증을 위한 인터페이스와 등록방법을 알아보았다.
하지만, 이 방법은 클라이언트의 요청에 대한 정밀한 검증이 필요할 때에는 유용할지 몰라도, 일반적으로 사용되고 자주 사용되는 검증은 정해져 있기 마련이다.
그런 간단한 검증마저도 모든 요청에 대해 개별적인 Validator를 등록하기에는 무리가 있다.
그래서 Bean Validation에서는 각종 일반적인 검증에 대해 애노테이션 형태로 사용할 수 있도록 제공한다.