본문 바로가기
Spring/series

[spring 20편] spring validate custom (spel 이용해서 validate 수행할지 결정)

by 무대포 개발자 2023. 1. 13.
728x90
반응형

source 는 Github 에 있습니다.

목차는 spring series 목차 에 있습니다.

[spring 20편] spring validate custom (spel 이용해서 validate 수행할지 결정)

spring validation custom

  • spel (Spring Expression Language) 을 이용해서 validate 을 수행할지 안할지를 결정하는 예제입니다.
  • 소스를 간략히 설명하면 ItemConstraint 를 선언하면 validation 이 수행됩니다. 이 때, condition 조건에 따라 validate 를 수행할지 안할지를 결정할 수 있습니다.
  • condition 조건은 spel (Spring Expression Language) 을 이용해서 제한적으로 체크합니다. (추후 보완하겠습니다.)

source

  • 테스트 예시를 하나 들어보면 testItemConstraintValidatorTest01 테스트는 조건을(condition) 먼저 체크합니다.
  • condition = "id =='ItemId'" --> parameter id 값이 "ItemId" 일 경우 validate 를 진행하겠다는 것입니다.
  • 비교할 필드는 "desc" 라는 필드이며, 해당 필드의 값이 compareValue 와 동일할 경우 validate 통과합니다.
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
// ItemConstraint.java
@Documented
@Retention(RUNTIME)
@Target({METHOD, FIELD, TYPE})
@Constraint(validatedBy = ItemConstraintValidator.class)
public @interface ItemConstraint {
String message() default "Invalid...";
String compareValue();
String field();
String condition();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// ItemConstraintValidator.java
@Slf4j
public class ItemConstraintValidator implements ConstraintValidator<ItemConstraint, Object> {
private Expression conditionExpression;
private String compareValue;
private String fieldName;
@Override
public void initialize(ItemConstraint itemConstraint) {
String condition = itemConstraint.condition();
ExpressionParser parser = new SpelExpressionParser();
if (StringUtils.hasText(condition)) {
conditionExpression = parser.parseExpression(condition);
}
this.compareValue = StringUtils.hasText(itemConstraint.compareValue()) ?
itemConstraint.compareValue() : "";
this.fieldName = StringUtils.hasText(itemConstraint.field()) ? itemConstraint.field() : "";
}
@Override
public boolean isValid(Object object, ConstraintValidatorContext context) {
EvaluationContext evaluationContext = createEvaluationContext(object);
if (ObjectUtils.isEmpty(object) || !isConditionPassing(evaluationContext)) {
return true;
}
log.info("pass condition... start validate constraint...");
return getFieldValue(object).equals(compareValue);
}
private Object getFieldValue(Object value) {
try {
Field field = ReflectionUtils.findField(value.getClass(), fieldName);
field.setAccessible(true);
return field.get(value);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
private boolean isConditionPassing(EvaluationContext evaluationContext) {
if (ObjectUtils.isEmpty(conditionExpression)) {
return true;
}
Boolean isCOnditionPassing = conditionExpression.getValue(evaluationContext, Boolean.class);
return ObjectUtils.isEmpty(isCOnditionPassing) ? false : isCOnditionPassing;
}
private StandardEvaluationContext createEvaluationContext(Object value) {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(value);
return context;
}
}
// ItemConstraintValidatorTest.java
class ItemConstraintValidatorTest {
@ParameterizedTest
@CsvSource({
"ItemId, ItemDesc, true", // pass condition --> valid compareValue
"ItemId, ItemDesc22, false" // pass condition --> invalid compareValue
})
@DisplayName("ItemCommandRequest 로 테스트")
void testItemConstraintValidatorTest01(String id, String desc, boolean expected)
throws NoSuchFieldException, IllegalAccessException {
var request = ItemCommandRequest.builder()
.id(id)
.desc(desc)
.build();
var validator = new ItemConstraintValidator();
var itemConstraint = request.getClass().getAnnotation(ItemConstraint.class);
validator.initialize(itemConstraint);
Assertions.assertEquals(expected, validator.isValid(request, getConstraintValidatorContext()));
}
@ParameterizedTest
@CsvSource({
"ItemId, Item123, true, false", // pass condition --> invalid compareValue
"ItemId, Item234, false, true" // not pass condition
})
@DisplayName("ItemCommandRequest2 로 테스트")
void testItemConstraintValidatorTest02(String id, String desc, boolean next, boolean expected)
throws NoSuchFieldException, IllegalAccessException {
var request = ItemCommandRequest2.builder()
.id(id)
.desc(desc)
.next(next)
.build();
var validator = new ItemConstraintValidator();
var itemConstraint = request.getClass().getAnnotation(ItemConstraint.class);
validator.initialize(itemConstraint);
Assertions.assertEquals(expected, validator.isValid(request, getConstraintValidatorContext()));
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ItemConstraint(compareValue = "ItemDesc", field = "desc", condition = "id == 'ItemId'")
public static class ItemCommandRequest {
private String id;
private String desc;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ItemConstraint(compareValue = "ItemDesc", field = "desc", condition = "id == 'ItemId' && next")
public static class ItemCommandRequest2 {
private String id;
private String desc;
private boolean next;
}
private ConstraintValidatorContext getConstraintValidatorContext() {
return new ConstraintValidatorContext() {
@Override
public void disableDefaultConstraintViolation() {
}
@Override
public String getDefaultConstraintMessageTemplate() {
return null;
}
@Override
public ClockProvider getClockProvider() {
return null;
}
@Override
public ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate) {
return null;
}
@Override
public <T> T unwrap(Class<T> type) {
return null;
}
};
}
}

댓글