diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java index 8d44afd963..a1d046b996 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java @@ -16,11 +16,8 @@ package org.springframework.boot.autoconfigure.validation; -import java.util.List; - import jakarta.validation.Validator; import jakarta.validation.executable.ExecutableValidator; -import jakarta.validation.valueextraction.ValueExtractor; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanDefinition; @@ -31,11 +28,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.validation.MessageInterpolatorFactory; -import org.springframework.boot.validation.beanvalidation.CustomizableLocalValidatorFactoryBean; import org.springframework.boot.validation.beanvalidation.FilteredMethodValidationPostProcessor; import org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter; -import org.springframework.boot.validation.beanvalidation.customize.AddValueExtractorCustomizer; -import org.springframework.boot.validation.beanvalidation.customize.Customizer; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; @@ -59,20 +53,14 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess @Import(PrimaryDefaultValidatorPostProcessor.class) public class ValidationAutoConfiguration { - @Bean - public static AddValueExtractorCustomizer autoAddValueExtractorCustomizer( - @SuppressWarnings({ "rawtypes", "unchecked" }) List valueExtractors) { - - return new AddValueExtractorCustomizer(valueExtractors); - } - @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @ConditionalOnMissingBean(Validator.class) public static LocalValidatorFactoryBean defaultValidator(ApplicationContext applicationContext, - List customizers) { - CustomizableLocalValidatorFactoryBean factoryBean = new CustomizableLocalValidatorFactoryBean(); - factoryBean.setCustomizers(customizers); + ObjectProvider customizers) { + LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); + factoryBean.setConfigurationInitializer((configuration) -> customizers.orderedStream() + .forEach((customizer) -> customizer.customize(configuration))); MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(applicationContext); factoryBean.setMessageInterpolator(interpolatorFactory.getObject()); return factoryBean; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/validation/beanvalidation/customize/Customizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationConfigurationCustomizer.java similarity index 72% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/validation/beanvalidation/customize/Customizer.java rename to spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationConfigurationCustomizer.java index f00c6a1dd6..e639cda846 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/validation/beanvalidation/customize/Customizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationConfigurationCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.validation.beanvalidation.customize; +package org.springframework.boot.autoconfigure.validation; import jakarta.validation.Configuration; @@ -22,12 +22,15 @@ import jakarta.validation.Configuration; * Callback interface that can be used to customize {@link Configuration}. * * @author Dang Zhicairang - * @since 2.6.2 - * @see AddValueExtractorCustomizer + * @since 3.0.0 */ @FunctionalInterface -public interface Customizer { +public interface ValidationConfigurationCustomizer { + /** + * Customize the given {@code configuration}. + * @param configuration the configuration to customize + */ void customize(Configuration configuration); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java index 717a7b18f0..6d9f801594 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java @@ -24,6 +24,8 @@ import jakarta.validation.Validator; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Size; import org.junit.jupiter.api.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -36,6 +38,7 @@ import org.springframework.boot.validation.beanvalidation.MethodValidationExclud import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.core.annotation.Order; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.validation.annotation.Validated; import org.springframework.validation.beanvalidation.CustomValidatorBean; @@ -46,6 +49,8 @@ import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBea import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; /** @@ -235,6 +240,19 @@ class ValidationAutoConfigurationTests { })); } + @Test + void configurationCustomizerBeansAreCalledInOrder() { + this.contextRunner.withUserConfiguration(ConfigurationCustomizersConfiguration.class).run((context) -> { + ValidationConfigurationCustomizer customizerOne = context.getBean("customizerOne", + ValidationConfigurationCustomizer.class); + ValidationConfigurationCustomizer customizerTwo = context.getBean("customizerTwo", + ValidationConfigurationCustomizer.class); + InOrder inOrder = Mockito.inOrder(customizerOne, customizerTwo); + then(customizerTwo).should(inOrder).customize(any(jakarta.validation.Configuration.class)); + then(customizerOne).should(inOrder).customize(any(jakarta.validation.Configuration.class)); + }); + } + private boolean isPrimaryBean(AssertableApplicationContext context, String beanName) { return ((BeanDefinitionRegistry) context.getSourceApplicationContext()).getBeanDefinition(beanName).isPrimary(); } @@ -421,4 +439,21 @@ class ValidationAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class ConfigurationCustomizersConfiguration { + + @Bean + @Order(1) + ValidationConfigurationCustomizer customizerOne() { + return mock(ValidationConfigurationCustomizer.class); + } + + @Bean + @Order(0) + ValidationConfigurationCustomizer customizerTwo() { + return mock(ValidationConfigurationCustomizer.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/validation.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/validation.adoc index f9561871b8..be6929a7bd 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/validation.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/validation.adoc @@ -11,3 +11,6 @@ include::code:MyBean[] The application's `MessageSource` is used when resolving `+{parameters}+` in constraint messages. This allows you to use <> for Bean Validation messages. Once the parameters have been resolved, message interpolation is completed using Bean Validation's default interpolator. + +To customize the `Configuration` used to build the `ValidatorFactory`, define a `ValidationConfigurationCustomizer` bean. +When multiple customizer beans are defined, they are called in order based on their `@Order` annotation or `Ordered` implementation. \ No newline at end of file diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/validation/beanvalidation/CustomizableLocalValidatorFactoryBean.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/validation/beanvalidation/CustomizableLocalValidatorFactoryBean.java deleted file mode 100644 index bc31f60ef9..0000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/validation/beanvalidation/CustomizableLocalValidatorFactoryBean.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2012-2020 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.validation.beanvalidation; - -import java.util.List; -import java.util.Optional; - -import jakarta.validation.Configuration; - -import org.springframework.boot.validation.beanvalidation.customize.Customizer; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; - -/** - * This class will callback the {@link Customizer} which supply by application. For - * example: - * - *
{@code
- * @Bean
- * public Customizer customizer() {
- *		return configuration -> {
- *			configuration.addValueExtractor(new CustomResultValueExtractor());
- *			configuration.xxx
- *			...
- *		};
- * }
- * }
- * - * @author Dang Zhicairang - * @since 2.6.2 - */ -public class CustomizableLocalValidatorFactoryBean extends LocalValidatorFactoryBean { - - private List customizers; - - public void setCustomizers(List customizers) { - this.customizers = customizers; - } - - @Override - protected void postProcessConfiguration(Configuration configuration) { - Optional.ofNullable(this.customizers) - .ifPresent((list) -> list.forEach((customizer) -> customizer.customize(configuration))); - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/validation/beanvalidation/customize/AddValueExtractorCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/validation/beanvalidation/customize/AddValueExtractorCustomizer.java deleted file mode 100644 index 6854d8933b..0000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/validation/beanvalidation/customize/AddValueExtractorCustomizer.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2012-2020 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.validation.beanvalidation.customize; - -import java.util.List; -import java.util.Optional; - -import jakarta.validation.Configuration; -import jakarta.validation.valueextraction.ValueExtractor; - -/** - * Add given collection of {@link ValueExtractor} into {@link Configuration}. - * - * @author Dang Zhicairang - * @since 2.6.2 - */ -@SuppressWarnings({ "rawtypes", "unchecked" }) -public class AddValueExtractorCustomizer implements Customizer { - - private List valueExtractors; - - public AddValueExtractorCustomizer(List valueExtractors) { - this.valueExtractors = valueExtractors; - } - - public List getValueExtractors() { - return this.valueExtractors; - } - - public void setValueExtractors(List valueExtractors) { - this.valueExtractors = valueExtractors; - } - - @Override - public void customize(Configuration configuration) { - Optional.ofNullable(this.getValueExtractors()) - .ifPresent((list) -> list.forEach(configuration::addValueExtractor)); - } - -}