diff --git a/spring-context/src/main/java/org/springframework/validation/Validator.java b/spring-context/src/main/java/org/springframework/validation/Validator.java
index b67b6d5d8b77..3ad6ade1b550 100644
--- a/spring-context/src/main/java/org/springframework/validation/Validator.java
+++ b/spring-context/src/main/java/org/springframework/validation/Validator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,10 @@
package org.springframework.validation;
+import java.util.function.BiConsumer;
+
+import org.springframework.util.Assert;
+
/**
* A validator for application-specific objects.
*
@@ -59,6 +63,7 @@
* application.
*
* @author Rod Johnson
+ * @author Toshiaki Maki
* @see SmartValidator
* @see Errors
* @see ValidationUtils
@@ -92,4 +97,38 @@ public interface Validator {
*/
void validate(Object target, Errors errors);
+ /**
+ * Takes the {@link BiConsumer} containing the validation logic for the specific type
+ * <T>
and returns the {@link Validator} instance.
+ * This validator implements the typical {@link #supports(Class)} method
+ * for the given <T>
.
+ *
+ * By using this method, a {@link Validator} can be implemented as follows:
+ *
+ *
Validator passwordEqualsValidator = Validator.of(PasswordResetForm.class, (form, errors) -> {
+ * if (!Objects.equals(form.getPassword(), form.getConfirmPassword())) {
+ * errors.rejectValue("confirmPassword",
+ * "PasswordEqualsValidator.passwordResetForm.password",
+ * "password and confirm password must be same.");
+ * }
+ * });
+ * @param targetClass the class of the object that is to be validated
+ * @param delegate the validation logic to delegate for the specific type <T>
+ * @param the type of the object that is to be validated
+ * @return the {@link Validator} instance
+ */
+ static Validator of(Class targetClass, BiConsumer delegate) {
+ Assert.notNull(targetClass, "'targetClass' must not be null.");
+ return new Validator() {
+ @Override
+ public boolean supports(Class> clazz) {
+ return targetClass.isAssignableFrom(clazz);
+ }
+
+ @Override
+ public void validate(Object target, Errors errors) {
+ delegate.accept(targetClass.cast(target), errors);
+ }
+ };
+ }
}
diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
index 05dd886da828..ccf887c87a45 100644
--- a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
+++ b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,6 +82,16 @@
*/
class DataBinderTests {
+ Validator spouseValidator = Validator.of(TestBean.class, (tb, errors) -> {
+ if (tb == null || "XXX".equals(tb.getName())) {
+ errors.rejectValue("", "SPOUSE_NOT_AVAILABLE");
+ return;
+ }
+ if (tb.getAge() < 32) {
+ errors.rejectValue("age", "TOO_YOUNG", "simply too young");
+ }
+ });
+
@Test
void bindingNoErrors() throws BindException {
TestBean rod = new TestBean();
@@ -1144,7 +1154,6 @@ void validatorNoErrors() throws Exception {
errors.setNestedPath("spouse");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
assertThat(errors.getFieldValue("age")).isEqualTo("argh");
- Validator spouseValidator = new SpouseValidator();
spouseValidator.validate(tb.getSpouse(), errors);
errors.setNestedPath("");
@@ -1195,7 +1204,6 @@ void validatorWithErrors() {
errors.setNestedPath("spouse.");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
- Validator spouseValidator = new SpouseValidator();
spouseValidator.validate(tb.getSpouse(), errors);
errors.setNestedPath("");
@@ -1267,7 +1275,6 @@ void validatorWithErrorsAndCodesPrefix() {
errors.setNestedPath("spouse.");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
- Validator spouseValidator = new SpouseValidator();
spouseValidator.validate(tb.getSpouse(), errors);
errors.setNestedPath("");
@@ -1332,7 +1339,6 @@ void validatorWithNestedObjectNull() {
testValidator.validate(tb, errors);
errors.setNestedPath("spouse.");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
- Validator spouseValidator = new SpouseValidator();
spouseValidator.validate(tb.getSpouse(), errors);
errors.setNestedPath("");
@@ -1348,7 +1354,6 @@ void nestedValidatorWithoutNestedPath() {
TestBean tb = new TestBean();
tb.setName("XXX");
Errors errors = new BeanPropertyBindingResult(tb, "tb");
- Validator spouseValidator = new SpouseValidator();
spouseValidator.validate(tb, errors);
assertThat(errors.hasGlobalErrors()).isTrue();
@@ -2160,28 +2165,6 @@ public void validate(@Nullable Object obj, Errors errors) {
}
}
-
- private static class SpouseValidator implements Validator {
-
- @Override
- public boolean supports(Class> clazz) {
- return TestBean.class.isAssignableFrom(clazz);
- }
-
- @Override
- public void validate(@Nullable Object obj, Errors errors) {
- TestBean tb = (TestBean) obj;
- if (tb == null || "XXX".equals(tb.getName())) {
- errors.rejectValue("", "SPOUSE_NOT_AVAILABLE");
- return;
- }
- if (tb.getAge() < 32) {
- errors.rejectValue("age", "TOO_YOUNG", "simply too young");
- }
- }
- }
-
-
@SuppressWarnings("unused")
private static class GrowingList extends AbstractList {
diff --git a/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java b/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java
index 0a027b95df43..bec3ff28ef27 100644
--- a/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java
+++ b/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.testfixture.beans.TestBean;
-import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -34,6 +33,10 @@
*/
public class ValidationUtilsTests {
+ Validator emptyValidator = Validator.of(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmpty(errors, "name", "EMPTY", "You must enter a name!"));
+
+ Validator emptyOrWhitespaceValidator = Validator.of(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "EMPTY_OR_WHITESPACE", "You must enter a name!"));
+
@Test
public void testInvokeValidatorWithNullValidator() throws Exception {
TestBean tb = new TestBean();
@@ -46,14 +49,14 @@ public void testInvokeValidatorWithNullValidator() throws Exception {
public void testInvokeValidatorWithNullErrors() throws Exception {
TestBean tb = new TestBean();
assertThatIllegalArgumentException().isThrownBy(() ->
- ValidationUtils.invokeValidator(new EmptyValidator(), tb, null));
+ ValidationUtils.invokeValidator(emptyValidator, tb, null));
}
@Test
public void testInvokeValidatorSunnyDay() throws Exception {
TestBean tb = new TestBean();
Errors errors = new BeanPropertyBindingResult(tb, "tb");
- ValidationUtils.invokeValidator(new EmptyValidator(), tb, errors);
+ ValidationUtils.invokeValidator(emptyValidator, tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
}
@@ -62,15 +65,14 @@ public void testInvokeValidatorSunnyDay() throws Exception {
public void testValidationUtilsSunnyDay() throws Exception {
TestBean tb = new TestBean("");
- Validator testValidator = new EmptyValidator();
tb.setName(" ");
Errors errors = new BeanPropertyBindingResult(tb, "tb");
- testValidator.validate(tb, errors);
+ emptyValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isFalse();
tb.setName("Roddy");
errors = new BeanPropertyBindingResult(tb, "tb");
- testValidator.validate(tb, errors);
+ emptyValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isFalse();
}
@@ -78,8 +80,7 @@ public void testValidationUtilsSunnyDay() throws Exception {
public void testValidationUtilsNull() throws Exception {
TestBean tb = new TestBean();
Errors errors = new BeanPropertyBindingResult(tb, "tb");
- Validator testValidator = new EmptyValidator();
- testValidator.validate(tb, errors);
+ emptyValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
}
@@ -88,8 +89,7 @@ public void testValidationUtilsNull() throws Exception {
public void testValidationUtilsEmpty() throws Exception {
TestBean tb = new TestBean("");
Errors errors = new BeanPropertyBindingResult(tb, "tb");
- Validator testValidator = new EmptyValidator();
- testValidator.validate(tb, errors);
+ emptyValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY");
}
@@ -115,32 +115,31 @@ public void testValidationUtilsEmptyVariants() {
@Test
public void testValidationUtilsEmptyOrWhitespace() throws Exception {
TestBean tb = new TestBean();
- Validator testValidator = new EmptyOrWhitespaceValidator();
// Test null
Errors errors = new BeanPropertyBindingResult(tb, "tb");
- testValidator.validate(tb, errors);
+ emptyOrWhitespaceValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
// Test empty String
tb.setName("");
errors = new BeanPropertyBindingResult(tb, "tb");
- testValidator.validate(tb, errors);
+ emptyOrWhitespaceValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
// Test whitespace String
tb.setName(" ");
errors = new BeanPropertyBindingResult(tb, "tb");
- testValidator.validate(tb, errors);
+ emptyOrWhitespaceValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isTrue();
assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE");
// Test OK
tb.setName("Roddy");
errors = new BeanPropertyBindingResult(tb, "tb");
- testValidator.validate(tb, errors);
+ emptyOrWhitespaceValidator.validate(tb, errors);
assertThat(errors.hasFieldErrors("name")).isFalse();
}
@@ -163,32 +162,4 @@ public void testValidationUtilsEmptyOrWhitespaceVariants() {
assertThat(errors.getFieldError("name").getDefaultMessage()).isEqualTo("msg");
}
-
- private static class EmptyValidator implements Validator {
-
- @Override
- public boolean supports(Class> clazz) {
- return TestBean.class.isAssignableFrom(clazz);
- }
-
- @Override
- public void validate(@Nullable Object obj, Errors errors) {
- ValidationUtils.rejectIfEmpty(errors, "name", "EMPTY", "You must enter a name!");
- }
- }
-
-
- private static class EmptyOrWhitespaceValidator implements Validator {
-
- @Override
- public boolean supports(Class> clazz) {
- return TestBean.class.isAssignableFrom(clazz);
- }
-
- @Override
- public void validate(@Nullable Object obj, Errors errors) {
- ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "EMPTY_OR_WHITESPACE", "You must enter a name!");
- }
- }
-
}