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 ccf276c5e6..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 @@ -56,8 +56,11 @@ public class ValidationAutoConfiguration { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @ConditionalOnMissingBean(Validator.class) - public static LocalValidatorFactoryBean defaultValidator(ApplicationContext applicationContext) { + public static LocalValidatorFactoryBean defaultValidator(ApplicationContext applicationContext, + 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-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationConfigurationCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationConfigurationCustomizer.java new file mode 100644 index 0000000000..e639cda846 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationConfigurationCustomizer.java @@ -0,0 +1,36 @@ +/* + * 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. + * 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.autoconfigure.validation; + +import jakarta.validation.Configuration; + +/** + * Callback interface that can be used to customize {@link Configuration}. + * + * @author Dang Zhicairang + * @since 3.0.0 + */ +@FunctionalInterface +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