Polish "Provide callback mechanism for customizing validation configuration"

See gh-29429
pull/30862/head
Andy Wilkinson 3 years ago
parent 76a1c6bcaa
commit 0e00fafe38

@ -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<ValueExtractor> valueExtractors) {
return new AddValueExtractorCustomizer(valueExtractors);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean(Validator.class)
public static LocalValidatorFactoryBean defaultValidator(ApplicationContext applicationContext,
List<Customizer> customizers) {
CustomizableLocalValidatorFactoryBean factoryBean = new CustomizableLocalValidatorFactoryBean();
factoryBean.setCustomizers(customizers);
ObjectProvider<ValidationConfigurationCustomizer> 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;

@ -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);
}

@ -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);
}
}
}

@ -11,3 +11,6 @@ include::code:MyBean[]
The application's `MessageSource` is used when resolving `+{parameters}+` in constraint messages.
This allows you to use <<features.adoc#features.internationalization,your application's `messages.properties` files>> 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.

@ -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:
*
* <pre>{@code
* &#64;Bean
* public Customizer customizer() {
* return configuration -> {
* configuration.addValueExtractor(new CustomResultValueExtractor());
* configuration.xxx
* ...
* };
* }
* }</pre>
*
* @author Dang Zhicairang
* @since 2.6.2
*/
public class CustomizableLocalValidatorFactoryBean extends LocalValidatorFactoryBean {
private List<Customizer> customizers;
public void setCustomizers(List<Customizer> customizers) {
this.customizers = customizers;
}
@Override
protected void postProcessConfiguration(Configuration<?> configuration) {
Optional.ofNullable(this.customizers)
.ifPresent((list) -> list.forEach((customizer) -> customizer.customize(configuration)));
}
}

@ -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<ValueExtractor> valueExtractors;
public AddValueExtractorCustomizer(List<ValueExtractor> valueExtractors) {
this.valueExtractors = valueExtractors;
}
public List<ValueExtractor> getValueExtractors() {
return this.valueExtractors;
}
public void setValueExtractors(List<ValueExtractor> valueExtractors) {
this.valueExtractors = valueExtractors;
}
@Override
public void customize(Configuration<?> configuration) {
Optional.ofNullable(this.getValueExtractors())
.ifPresent((list) -> list.forEach(configuration::addValueExtractor));
}
}
Loading…
Cancel
Save