Skip to content

Commit 35de7e1

Browse files
committed
Introduce initializer callback for Bean Validation Configuration
Closes gh-27956
1 parent 10e979e commit 35de7e1

File tree

3 files changed

+146
-34
lines changed

3 files changed

+146
-34
lines changed

Diff for: spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/ValidatorFactoryTests.java

+90-28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
import javax.validation.Constraint;
3232
import javax.validation.ConstraintValidator;
3333
import javax.validation.ConstraintValidatorContext;
34+
import javax.validation.ConstraintValidatorFactory;
3435
import javax.validation.ConstraintViolation;
3536
import javax.validation.Payload;
3637
import javax.validation.Valid;
@@ -43,6 +44,7 @@
4344
import org.junit.jupiter.api.Test;
4445

4546
import org.springframework.beans.factory.annotation.Autowired;
47+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
4648
import org.springframework.context.ConfigurableApplicationContext;
4749
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
4850
import org.springframework.core.convert.support.DefaultConversionService;
@@ -52,18 +54,18 @@
5254
import org.springframework.validation.FieldError;
5355
import org.springframework.validation.ObjectError;
5456
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
57+
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
5558

5659
import static org.assertj.core.api.Assertions.assertThat;
5760

5861
/**
5962
* @author Juergen Hoeller
6063
*/
61-
@SuppressWarnings("resource")
62-
public class ValidatorFactoryTests {
64+
class ValidatorFactoryTests {
6365

6466
@Test
65-
@SuppressWarnings("cast")
66-
public void testSimpleValidation() {
67+
void simpleValidation() {
68+
@SuppressWarnings("resource")
6769
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
6870
validator.afterPropertiesSet();
6971

@@ -78,15 +80,15 @@ public void testSimpleValidation() {
7880

7981
Validator nativeValidator = validator.unwrap(Validator.class);
8082
assertThat(nativeValidator.getClass().getName().startsWith("org.hibernate")).isTrue();
81-
assertThat(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
82-
assertThat(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
83+
assertThat(validator.unwrap(ValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
84+
assertThat(validator.unwrap(HibernateValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
8385

8486
validator.destroy();
8587
}
8688

8789
@Test
88-
@SuppressWarnings("cast")
89-
public void testSimpleValidationWithCustomProvider() {
90+
void simpleValidationWithCustomProvider() {
91+
@SuppressWarnings("resource")
9092
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
9193
validator.setProviderClass(HibernateValidator.class);
9294
validator.afterPropertiesSet();
@@ -102,14 +104,15 @@ public void testSimpleValidationWithCustomProvider() {
102104

103105
Validator nativeValidator = validator.unwrap(Validator.class);
104106
assertThat(nativeValidator.getClass().getName().startsWith("org.hibernate")).isTrue();
105-
assertThat(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
106-
assertThat(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
107+
assertThat(validator.unwrap(ValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
108+
assertThat(validator.unwrap(HibernateValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
107109

108110
validator.destroy();
109111
}
110112

111113
@Test
112-
public void testSimpleValidationWithClassLevel() {
114+
void simpleValidationWithClassLevel() {
115+
@SuppressWarnings("resource")
113116
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
114117
validator.afterPropertiesSet();
115118

@@ -122,10 +125,13 @@ public void testSimpleValidationWithClassLevel() {
122125
ConstraintViolation<?> cv = iterator.next();
123126
assertThat(cv.getPropertyPath().toString()).isEqualTo("");
124127
assertThat(cv.getConstraintDescriptor().getAnnotation() instanceof NameAddressValid).isTrue();
128+
129+
validator.destroy();
125130
}
126131

127132
@Test
128-
public void testSpringValidationFieldType() {
133+
void springValidationFieldType() {
134+
@SuppressWarnings("resource")
129135
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
130136
validator.afterPropertiesSet();
131137

@@ -135,11 +141,16 @@ public void testSpringValidationFieldType() {
135141
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(person, "person");
136142
validator.validate(person, errors);
137143
assertThat(errors.getErrorCount()).isEqualTo(1);
138-
assertThat(errors.getFieldError("address").getRejectedValue()).isInstanceOf(ValidAddress.class);
144+
assertThat(errors.getFieldError("address").getRejectedValue())
145+
.as("Field/Value type mismatch")
146+
.isInstanceOf(ValidAddress.class);
147+
148+
validator.destroy();
139149
}
140150

141151
@Test
142-
public void testSpringValidation() {
152+
void springValidation() {
153+
@SuppressWarnings("resource")
143154
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
144155
validator.afterPropertiesSet();
145156

@@ -164,10 +175,13 @@ public void testSpringValidation() {
164175
assertThat(errorCodes.contains("NotNull.street")).isTrue();
165176
assertThat(errorCodes.contains("NotNull.java.lang.String")).isTrue();
166177
assertThat(errorCodes.contains("NotNull")).isTrue();
178+
179+
validator.destroy();
167180
}
168181

169182
@Test
170-
public void testSpringValidationWithClassLevel() {
183+
void springValidationWithClassLevel() {
184+
@SuppressWarnings("resource")
171185
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
172186
validator.afterPropertiesSet();
173187

@@ -182,10 +196,12 @@ public void testSpringValidationWithClassLevel() {
182196
assertThat(errorCodes.size()).isEqualTo(2);
183197
assertThat(errorCodes.contains("NameAddressValid.person")).isTrue();
184198
assertThat(errorCodes.contains("NameAddressValid")).isTrue();
199+
200+
validator.destroy();
185201
}
186202

187203
@Test
188-
public void testSpringValidationWithAutowiredValidator() {
204+
void springValidationWithAutowiredValidator() {
189205
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(
190206
LocalValidatorFactoryBean.class);
191207
LocalValidatorFactoryBean validator = ctx.getBean(LocalValidatorFactoryBean.class);
@@ -202,11 +218,14 @@ public void testSpringValidationWithAutowiredValidator() {
202218
assertThat(errorCodes.size()).isEqualTo(2);
203219
assertThat(errorCodes.contains("NameAddressValid.person")).isTrue();
204220
assertThat(errorCodes.contains("NameAddressValid")).isTrue();
221+
222+
validator.destroy();
205223
ctx.close();
206224
}
207225

208226
@Test
209-
public void testSpringValidationWithErrorInListElement() {
227+
void springValidationWithErrorInListElement() {
228+
@SuppressWarnings("resource")
210229
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
211230
validator.afterPropertiesSet();
212231

@@ -221,10 +240,13 @@ public void testSpringValidationWithErrorInListElement() {
221240
assertThat(fieldError.getField()).isEqualTo("address.street");
222241
fieldError = result.getFieldError("addressList[0].street");
223242
assertThat(fieldError.getField()).isEqualTo("addressList[0].street");
243+
244+
validator.destroy();
224245
}
225246

226247
@Test
227-
public void testSpringValidationWithErrorInSetElement() {
248+
void springValidationWithErrorInSetElement() {
249+
@SuppressWarnings("resource")
228250
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
229251
validator.afterPropertiesSet();
230252

@@ -239,10 +261,13 @@ public void testSpringValidationWithErrorInSetElement() {
239261
assertThat(fieldError.getField()).isEqualTo("address.street");
240262
fieldError = result.getFieldError("addressSet[].street");
241263
assertThat(fieldError.getField()).isEqualTo("addressSet[].street");
264+
265+
validator.destroy();
242266
}
243267

244268
@Test
245-
public void testInnerBeanValidation() {
269+
void innerBeanValidation() {
270+
@SuppressWarnings("resource")
246271
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
247272
validator.afterPropertiesSet();
248273

@@ -251,10 +276,13 @@ public void testInnerBeanValidation() {
251276
validator.validate(mainBean, errors);
252277
Object rejected = errors.getFieldValue("inner.value");
253278
assertThat(rejected).isNull();
279+
280+
validator.destroy();
254281
}
255282

256283
@Test
257-
public void testValidationWithOptionalField() {
284+
void validationWithOptionalField() {
285+
@SuppressWarnings("resource")
258286
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
259287
validator.afterPropertiesSet();
260288

@@ -263,10 +291,13 @@ public void testValidationWithOptionalField() {
263291
validator.validate(mainBean, errors);
264292
Object rejected = errors.getFieldValue("inner.value");
265293
assertThat(rejected).isNull();
294+
295+
validator.destroy();
266296
}
267297

268298
@Test
269-
public void testListValidation() {
299+
void listValidation() {
300+
@SuppressWarnings("resource")
270301
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
271302
validator.afterPropertiesSet();
272303

@@ -282,6 +313,34 @@ public void testListValidation() {
282313
assertThat(fieldError).isNotNull();
283314
assertThat(fieldError.getRejectedValue()).isEqualTo("X");
284315
assertThat(errors.getFieldValue("list[1]")).isEqualTo("X");
316+
317+
validator.destroy();
318+
}
319+
320+
@Test
321+
void withConstraintValidatorFactory() {
322+
ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
323+
324+
@SuppressWarnings("resource")
325+
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
326+
validator.setConstraintValidatorFactory(cvf);
327+
validator.afterPropertiesSet();
328+
329+
assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
330+
validator.destroy();
331+
}
332+
333+
@Test
334+
void withCustomInitializer() {
335+
ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
336+
337+
@SuppressWarnings("resource")
338+
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
339+
validator.setConfigurationInitializer(configuration -> configuration.constraintValidatorFactory(cvf));
340+
validator.afterPropertiesSet();
341+
342+
assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
343+
validator.destroy();
285344
}
286345

287346

@@ -380,8 +439,8 @@ public boolean isValid(ValidPerson value, ConstraintValidatorContext context) {
380439
}
381440
boolean valid = (value.name == null || !value.address.street.contains(value.name));
382441
if (!valid && "Phil".equals(value.name)) {
383-
context.buildConstraintViolationWithTemplate(
384-
context.getDefaultConstraintMessageTemplate()).addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
442+
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
443+
.addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
385444
}
386445
return valid;
387446
}
@@ -417,6 +476,7 @@ public static class InnerBean {
417476
public String getValue() {
418477
return value;
419478
}
479+
420480
public void setValue(String value) {
421481
this.value = value;
422482
}
@@ -425,8 +485,8 @@ public void setValue(String value) {
425485

426486
@Retention(RetentionPolicy.RUNTIME)
427487
@Target(ElementType.FIELD)
428-
@Constraint(validatedBy=InnerValidator.class)
429-
public static @interface InnerValid {
488+
@Constraint(validatedBy = InnerValidator.class)
489+
public @interface InnerValid {
430490

431491
String message() default "NOT VALID";
432492

@@ -446,7 +506,8 @@ public void initialize(InnerValid constraintAnnotation) {
446506
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
447507
context.disableDefaultConstraintViolation();
448508
if (bean.getValue() == null) {
449-
context.buildConstraintViolationWithTemplate("NULL").addPropertyNode("value").addConstraintViolation();
509+
context.buildConstraintViolationWithTemplate("NULL")
510+
.addPropertyNode("value").addConstraintViolation();
450511
return false;
451512
}
452513
return true;
@@ -494,7 +555,8 @@ public boolean isValid(List<String> list, ConstraintValidatorContext context) {
494555
boolean valid = true;
495556
for (int i = 0; i < list.size(); i++) {
496557
if ("X".equals(list.get(i))) {
497-
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addBeanNode().inIterable().atIndex(i).addConstraintViolation();
558+
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
559+
.addBeanNode().inIterable().atIndex(i).addConstraintViolation();
498560
valid = false;
499561
}
500562
}

Diff for: spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import java.util.List;
2727
import java.util.Map;
2828
import java.util.Properties;
29+
import java.util.function.Consumer;
2930

3031
import javax.validation.Configuration;
3132
import javax.validation.ConstraintValidatorFactory;
@@ -113,6 +114,9 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
113114

114115
private final Map<String, String> validationPropertyMap = new HashMap<>();
115116

117+
@Nullable
118+
private Consumer<Configuration<?>> configurationInitializer;
119+
116120
@Nullable
117121
private ApplicationContext applicationContext;
118122

@@ -234,6 +238,18 @@ public Map<String, String> getValidationPropertyMap() {
234238
return this.validationPropertyMap;
235239
}
236240

241+
/**
242+
* Specify a callback for customizing the Bean Validation {@code Configuration} instance,
243+
* as an alternative to overriding the {@link #postProcessConfiguration(Configuration)}
244+
* method in custom {@code LocalValidatorFactoryBean} subclasses.
245+
* <p>This enables convenient customizations for application purposes. Infrastructure
246+
* extensions may keep overriding the {@link #postProcessConfiguration} template method.
247+
* @since 5.3.19
248+
*/
249+
public void setConfigurationInitializer(Consumer<Configuration<?>> configurationInitializer) {
250+
this.configurationInitializer = configurationInitializer;
251+
}
252+
237253
@Override
238254
public void setApplicationContext(ApplicationContext applicationContext) {
239255
this.applicationContext = applicationContext;
@@ -312,6 +328,9 @@ public void afterPropertiesSet() {
312328
this.validationPropertyMap.forEach(configuration::addProperty);
313329

314330
// Allow for custom post-processing before we actually build the ValidatorFactory.
331+
if (this.configurationInitializer != null) {
332+
this.configurationInitializer.accept(configuration);
333+
}
315334
postProcessConfiguration(configuration);
316335

317336
try {

0 commit comments

Comments
 (0)