Create spring-boot-validation module
This commit is contained in:
committed by
Phillip Webb
parent
aa11d6d0fb
commit
fc3ae4f975
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure;
|
||||
|
||||
import jakarta.validation.Configuration;
|
||||
import jakarta.validation.Validation;
|
||||
|
||||
import org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializer;
|
||||
|
||||
/**
|
||||
* {@link BackgroundPreinitializer} for jakarta.validation.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
final class JakartaValidationBackgroundPreinitializer implements BackgroundPreinitializer {
|
||||
|
||||
@Override
|
||||
public void preinitialize() throws Exception {
|
||||
Configuration<?> configuration = Validation.byDefaultProvider().configure();
|
||||
configuration.buildValidatorFactory().getValidator();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
/**
|
||||
* Enable the {@code Primary} flag on the auto-configured validator if necessary.
|
||||
* <p>
|
||||
* As {@link LocalValidatorFactoryBean} exposes 3 validator related contracts and we're
|
||||
* only checking for the absence {@link jakarta.validation.Validator}, we should flag the
|
||||
* auto-configured validator as primary only if no Spring's {@link Validator} is flagged
|
||||
* as primary.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Matej Nedic
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class PrimaryDefaultValidatorPostProcessor implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
|
||||
|
||||
/**
|
||||
* The bean name of the auto-configured Validator.
|
||||
*/
|
||||
private static final String VALIDATOR_BEAN_NAME = "defaultValidator";
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
if (beanFactory instanceof ConfigurableListableBeanFactory listableBeanFactory) {
|
||||
this.beanFactory = listableBeanFactory;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
BeanDefinition definition = getAutoConfiguredValidator(registry);
|
||||
if (definition != null) {
|
||||
definition.setPrimary(!hasPrimarySpringValidator());
|
||||
}
|
||||
}
|
||||
|
||||
private BeanDefinition getAutoConfiguredValidator(BeanDefinitionRegistry registry) {
|
||||
if (registry.containsBeanDefinition(VALIDATOR_BEAN_NAME)) {
|
||||
BeanDefinition definition = registry.getBeanDefinition(VALIDATOR_BEAN_NAME);
|
||||
if (definition.getRole() == BeanDefinition.ROLE_INFRASTRUCTURE
|
||||
&& isTypeMatch(VALIDATOR_BEAN_NAME, LocalValidatorFactoryBean.class)) {
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isTypeMatch(String name, Class<?> type) {
|
||||
return this.beanFactory != null && this.beanFactory.isTypeMatch(name, type);
|
||||
}
|
||||
|
||||
private boolean hasPrimarySpringValidator() {
|
||||
String[] validatorBeans = this.beanFactory.getBeanNamesForType(Validator.class, false, false);
|
||||
for (String validatorBean : validatorBeans) {
|
||||
BeanDefinition definition = this.beanFactory.getBeanDefinition(validatorBean);
|
||||
if (definition.isPrimary()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure;
|
||||
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.executable.ExecutableValidator;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
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.FilteredMethodValidationPostProcessor;
|
||||
import org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} to configure the validation
|
||||
* infrastructure.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
* @author Yanming Zhou
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(ExecutableValidator.class)
|
||||
@ConditionalOnResource(resources = "classpath:META-INF/services/jakarta.validation.spi.ValidationProvider")
|
||||
@Import(PrimaryDefaultValidatorPostProcessor.class)
|
||||
public class ValidationAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
@ConditionalOnMissingBean(Validator.class)
|
||||
public static LocalValidatorFactoryBean defaultValidator(ApplicationContext applicationContext,
|
||||
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;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
|
||||
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
|
||||
ObjectProvider<Validator> validator, ObjectProvider<MethodValidationExcludeFilter> excludeFilters) {
|
||||
FilteredMethodValidationPostProcessor processor = new FilteredMethodValidationPostProcessor(
|
||||
excludeFilters.orderedStream());
|
||||
boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
|
||||
processor.setProxyTargetClass(proxyTargetClass);
|
||||
boolean adaptConstraintViolations = environment
|
||||
.getProperty("spring.validation.method.adapt-constraint-violations", Boolean.class, false);
|
||||
processor.setAdaptConstraintViolations(adaptConstraintViolations);
|
||||
processor.setValidatorProvider(validator);
|
||||
return processor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure;
|
||||
|
||||
import jakarta.validation.Configuration;
|
||||
|
||||
/**
|
||||
* Callback interface that can be used to customize {@link Configuration}.
|
||||
*
|
||||
* @author Dang Zhicairang
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ValidationConfigurationCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the given {@code configuration}.
|
||||
* @param configuration the configuration to customize
|
||||
*/
|
||||
void customize(Configuration<?> configuration);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.validation.MessageInterpolatorFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.SmartValidator;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
|
||||
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
|
||||
|
||||
/**
|
||||
* {@link Validator} implementation that delegates calls to another {@link Validator}.
|
||||
* This {@link Validator} implements Spring's {@link SmartValidator} interface but does
|
||||
* not implement the JSR-303 {@code jakarta.validator.Validator} interface.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @author Zisis Pavloudis
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class ValidatorAdapter implements SmartValidator, ApplicationContextAware, InitializingBean, DisposableBean {
|
||||
|
||||
private final SmartValidator target;
|
||||
|
||||
private final boolean existingBean;
|
||||
|
||||
ValidatorAdapter(SmartValidator target, boolean existingBean) {
|
||||
this.target = target;
|
||||
this.existingBean = existingBean;
|
||||
}
|
||||
|
||||
public final Validator getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> type) {
|
||||
return this.target.supports(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
this.target.validate(target, errors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors, Object... validationHints) {
|
||||
this.target.validate(target, errors, validationHints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
if (!this.existingBean && this.target instanceof ApplicationContextAware contextAwareTarget) {
|
||||
contextAwareTarget.setApplicationContext(applicationContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (!this.existingBean && this.target instanceof InitializingBean initializingBean) {
|
||||
initializingBean.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if (!this.existingBean && this.target instanceof DisposableBean disposableBean) {
|
||||
disposableBean.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Validator} that only implements the {@link Validator} interface,
|
||||
* wrapping it if necessary.
|
||||
* <p>
|
||||
* If the specified {@link Validator} is not {@code null}, it is wrapped. If not, a
|
||||
* {@link jakarta.validation.Validator} is retrieved from the context and wrapped.
|
||||
* Otherwise, a new default validator is created.
|
||||
* @param applicationContext the application context
|
||||
* @param validator an existing validator to use or {@code null}
|
||||
* @return the validator to use
|
||||
*/
|
||||
public static Validator get(ApplicationContext applicationContext, Validator validator) {
|
||||
if (validator != null) {
|
||||
return wrap(validator, false);
|
||||
}
|
||||
return getExistingOrCreate(applicationContext);
|
||||
}
|
||||
|
||||
private static Validator getExistingOrCreate(ApplicationContext applicationContext) {
|
||||
Validator existing = getExisting(applicationContext);
|
||||
if (existing != null) {
|
||||
return wrap(existing, true);
|
||||
}
|
||||
return create(applicationContext);
|
||||
}
|
||||
|
||||
private static Validator getExisting(ApplicationContext applicationContext) {
|
||||
try {
|
||||
jakarta.validation.Validator validatorBean = applicationContext.getBean(jakarta.validation.Validator.class);
|
||||
if (validatorBean instanceof Validator validator) {
|
||||
return validator;
|
||||
}
|
||||
return new SpringValidatorAdapter(validatorBean);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Validator create(MessageSource messageSource) {
|
||||
OptionalValidatorFactoryBean validator = new OptionalValidatorFactoryBean();
|
||||
try {
|
||||
MessageInterpolatorFactory factory = new MessageInterpolatorFactory(messageSource);
|
||||
validator.setMessageInterpolator(factory.getObject());
|
||||
}
|
||||
catch (ValidationException ex) {
|
||||
// Ignore
|
||||
}
|
||||
return wrap(validator, false);
|
||||
}
|
||||
|
||||
private static Validator wrap(Validator validator, boolean existingBean) {
|
||||
if (validator instanceof jakarta.validation.Validator jakartaValidator) {
|
||||
if (jakartaValidator instanceof SpringValidatorAdapter adapter) {
|
||||
return new ValidatorAdapter(adapter, existingBean);
|
||||
}
|
||||
return new ValidatorAdapter(new SpringValidatorAdapter(jakartaValidator), existingBean);
|
||||
}
|
||||
return validator;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T unwrap(Class<T> type) {
|
||||
if (type.isInstance(this.target)) {
|
||||
return (T) this.target;
|
||||
}
|
||||
return this.target.unwrap(type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for (JSR-303) Validation.
|
||||
*/
|
||||
package org.springframework.boot.validation.autoconfigure;
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"groups": [],
|
||||
"properties": [
|
||||
{
|
||||
"name": "spring.validation.method.adapt-constraint-violations",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to adapt ConstraintViolations to MethodValidationResult.",
|
||||
"defaultValue": false
|
||||
}
|
||||
],
|
||||
"hints": []
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Background Preinitializers
|
||||
org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializer=\
|
||||
org.springframework.boot.validation.autoconfigure.JakartaValidationBackgroundPreinitializer
|
||||
@@ -0,0 +1 @@
|
||||
org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration
|
||||
@@ -0,0 +1,486 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
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;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.validation.autoconfigure.ValidationAutoConfigurationTests.CustomValidatorConfiguration.TestBeanPostProcessor;
|
||||
import org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter;
|
||||
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;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
|
||||
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
|
||||
import org.springframework.validation.method.MethodValidationException;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tests for {@link ValidationAutoConfiguration}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class ValidationAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ValidationAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void validationAutoConfigurationShouldConfigureDefaultValidator() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context.getBeanNamesForType(Validator.class)).containsExactly("defaultValidator");
|
||||
assertThat(context.getBeanNamesForType(org.springframework.validation.Validator.class))
|
||||
.containsExactly("defaultValidator");
|
||||
assertThat(context.getBean(Validator.class)).isInstanceOf(LocalValidatorFactoryBean.class)
|
||||
.isEqualTo(context.getBean(org.springframework.validation.Validator.class));
|
||||
assertThat(isPrimaryBean(context, "defaultValidator")).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationAutoConfigurationWhenUserProvidesValidatorShouldBackOff() {
|
||||
this.contextRunner.withUserConfiguration(UserDefinedValidatorConfig.class).run((context) -> {
|
||||
assertThat(context.getBeanNamesForType(Validator.class)).containsExactly("customValidator");
|
||||
assertThat(context.getBeanNamesForType(org.springframework.validation.Validator.class))
|
||||
.containsExactly("customValidator");
|
||||
assertThat(context.getBean(Validator.class)).isInstanceOf(OptionalValidatorFactoryBean.class)
|
||||
.isEqualTo(context.getBean(org.springframework.validation.Validator.class));
|
||||
assertThat(isPrimaryBean(context, "customValidator")).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationAutoConfigurationWhenUserProvidesDefaultValidatorShouldNotEnablePrimary() {
|
||||
this.contextRunner.withUserConfiguration(UserDefinedDefaultValidatorConfig.class).run((context) -> {
|
||||
assertThat(context.getBeanNamesForType(Validator.class)).containsExactly("defaultValidator");
|
||||
assertThat(context.getBeanNamesForType(org.springframework.validation.Validator.class))
|
||||
.containsExactly("defaultValidator");
|
||||
assertThat(isPrimaryBean(context, "defaultValidator")).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationAutoConfigurationWhenUserProvidesJsrValidatorShouldBackOff() {
|
||||
this.contextRunner.withUserConfiguration(UserDefinedJsrValidatorConfig.class).run((context) -> {
|
||||
assertThat(context.getBeanNamesForType(Validator.class)).containsExactly("customValidator");
|
||||
assertThat(context.getBeanNamesForType(org.springframework.validation.Validator.class)).isEmpty();
|
||||
assertThat(isPrimaryBean(context, "customValidator")).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationAutoConfigurationWhenUserProvidesSpringValidatorShouldCreateJsrValidator() {
|
||||
this.contextRunner.withUserConfiguration(UserDefinedSpringValidatorConfig.class).run((context) -> {
|
||||
assertThat(context.getBeanNamesForType(Validator.class)).containsExactly("defaultValidator");
|
||||
assertThat(context.getBeanNamesForType(org.springframework.validation.Validator.class))
|
||||
.containsExactly("customValidator", "anotherCustomValidator", "defaultValidator");
|
||||
assertThat(context.getBean(Validator.class)).isInstanceOf(LocalValidatorFactoryBean.class)
|
||||
.isEqualTo(context.getBean(org.springframework.validation.Validator.class));
|
||||
assertThat(isPrimaryBean(context, "defaultValidator")).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationAutoConfigurationWhenUserProvidesPrimarySpringValidatorShouldRemovePrimaryFlag() {
|
||||
this.contextRunner.withUserConfiguration(UserDefinedPrimarySpringValidatorConfig.class).run((context) -> {
|
||||
assertThat(context.getBeanNamesForType(Validator.class)).containsExactly("defaultValidator");
|
||||
assertThat(context.getBeanNamesForType(org.springframework.validation.Validator.class))
|
||||
.containsExactly("customValidator", "anotherCustomValidator", "defaultValidator");
|
||||
assertThat(context.getBean(Validator.class)).isInstanceOf(LocalValidatorFactoryBean.class);
|
||||
assertThat(context.getBean(org.springframework.validation.Validator.class))
|
||||
.isEqualTo(context.getBean("anotherCustomValidator"));
|
||||
assertThat(isPrimaryBean(context, "defaultValidator")).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenUserProvidesSpringValidatorInParentContextThenAutoConfiguredValidatorIsPrimary() {
|
||||
new ApplicationContextRunner().withUserConfiguration(UserDefinedSpringValidatorConfig.class).run((parent) -> {
|
||||
this.contextRunner.withParent(parent).run((context) -> {
|
||||
assertThat(context.getBeanNamesForType(Validator.class)).containsExactly("defaultValidator");
|
||||
assertThat(context.getBeanNamesForType(org.springframework.validation.Validator.class))
|
||||
.containsExactly("defaultValidator");
|
||||
assertThat(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context.getBeanFactory(),
|
||||
org.springframework.validation.Validator.class))
|
||||
.containsExactly("defaultValidator", "customValidator", "anotherCustomValidator");
|
||||
assertThat(isPrimaryBean(context, "defaultValidator")).isTrue();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenUserProvidesPrimarySpringValidatorInParentContextThenAutoConfiguredValidatorIsPrimary() {
|
||||
new ApplicationContextRunner().withUserConfiguration(UserDefinedPrimarySpringValidatorConfig.class)
|
||||
.run((parent) -> {
|
||||
this.contextRunner.withParent(parent).run((context) -> {
|
||||
assertThat(context.getBeanNamesForType(Validator.class)).containsExactly("defaultValidator");
|
||||
assertThat(context.getBeanNamesForType(org.springframework.validation.Validator.class))
|
||||
.containsExactly("defaultValidator");
|
||||
assertThat(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context.getBeanFactory(),
|
||||
org.springframework.validation.Validator.class))
|
||||
.containsExactly("defaultValidator", "customValidator", "anotherCustomValidator");
|
||||
assertThat(isPrimaryBean(context, "defaultValidator")).isTrue();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationIsEnabled() {
|
||||
this.contextRunner.withUserConfiguration(SampleService.class).run((context) -> {
|
||||
assertThat(context.getBeansOfType(Validator.class)).hasSize(1);
|
||||
SampleService service = context.getBean(SampleService.class);
|
||||
service.doSomething("Valid");
|
||||
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> service.doSomething("KO"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void classCanBeExcludedFromValidation() {
|
||||
this.contextRunner.withUserConfiguration(ExcludedServiceConfiguration.class).run((context) -> {
|
||||
assertThat(context.getBeansOfType(Validator.class)).hasSize(1);
|
||||
ExcludedService service = context.getBean(ExcludedService.class);
|
||||
service.doSomething("Valid");
|
||||
assertThatNoException().isThrownBy(() -> service.doSomething("KO"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationUsesCglibProxy() {
|
||||
this.contextRunner.withUserConfiguration(DefaultAnotherSampleService.class).run((context) -> {
|
||||
assertThat(context.getBeansOfType(Validator.class)).hasSize(1);
|
||||
DefaultAnotherSampleService service = context.getBean(DefaultAnotherSampleService.class);
|
||||
service.doSomething(42);
|
||||
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> service.doSomething(2));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationCanBeConfiguredToUseJdkProxy() {
|
||||
this.contextRunner.withUserConfiguration(AnotherSampleServiceConfiguration.class)
|
||||
.withPropertyValues("spring.aop.proxy-target-class=false")
|
||||
.run((context) -> {
|
||||
assertThat(context.getBeansOfType(Validator.class)).hasSize(1);
|
||||
assertThat(context.getBeansOfType(DefaultAnotherSampleService.class)).isEmpty();
|
||||
AnotherSampleService service = context.getBean(AnotherSampleService.class);
|
||||
service.doSomething(42);
|
||||
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> service.doSomething(2));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationCanBeConfiguredToAdaptConstraintViolations() {
|
||||
this.contextRunner.withUserConfiguration(AnotherSampleServiceConfiguration.class)
|
||||
.withPropertyValues("spring.validation.method.adapt-constraint-violations=true")
|
||||
.run((context) -> {
|
||||
assertThat(context.getBeansOfType(Validator.class)).hasSize(1);
|
||||
AnotherSampleService service = context.getBean(AnotherSampleService.class);
|
||||
service.doSomething(42);
|
||||
assertThatExceptionOfType(MethodValidationException.class).isThrownBy(() -> service.doSomething(2));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationUseDefaultAdaptToConstraintViolationsValue() {
|
||||
this.contextRunner.withUserConfiguration(AnotherSampleServiceConfiguration.class).run((context) -> {
|
||||
MethodValidationPostProcessor postProcessor = context.getBean(MethodValidationPostProcessor.class);
|
||||
assertThat(postProcessor).hasFieldOrPropertyWithValue("adaptConstraintViolations", false);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void userDefinedMethodValidationPostProcessorTakesPrecedence() {
|
||||
this.contextRunner.withUserConfiguration(SampleConfiguration.class).run((context) -> {
|
||||
assertThat(context.getBeansOfType(Validator.class)).hasSize(1);
|
||||
Object userMethodValidationPostProcessor = context.getBean("testMethodValidationPostProcessor");
|
||||
assertThat(context.getBean(MethodValidationPostProcessor.class))
|
||||
.isSameAs(userMethodValidationPostProcessor);
|
||||
assertThat(context.getBeansOfType(MethodValidationPostProcessor.class)).hasSize(1);
|
||||
Object validator = ReflectionTestUtils.getField(userMethodValidationPostProcessor, "validator");
|
||||
assertThat(validator).isInstanceOf(Supplier.class);
|
||||
assertThat(context.getBean(Validator.class)).isNotSameAs(((Supplier<Validator>) validator).get());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodValidationPostProcessorValidatorDependencyDoesNotTriggerEarlyInitialization() {
|
||||
this.contextRunner.withUserConfiguration(CustomValidatorConfiguration.class)
|
||||
.run((context) -> assertThat(context.getBean(TestBeanPostProcessor.class).postProcessed)
|
||||
.contains("someService"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationIsEnabledInChildContext() {
|
||||
this.contextRunner.run((parent) -> new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ValidationAutoConfiguration.class))
|
||||
.withUserConfiguration(SampleService.class)
|
||||
.withParent(parent)
|
||||
.run((context) -> {
|
||||
assertThat(context.getBeansOfType(Validator.class)).isEmpty();
|
||||
assertThat(parent.getBeansOfType(Validator.class)).hasSize(1);
|
||||
SampleService service = context.getBean(SampleService.class);
|
||||
service.doSomething("Valid");
|
||||
assertThatExceptionOfType(ConstraintViolationException.class)
|
||||
.isThrownBy(() -> service.doSomething("KO"));
|
||||
}));
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class UserDefinedValidatorConfig {
|
||||
|
||||
@Bean
|
||||
OptionalValidatorFactoryBean customValidator() {
|
||||
return new OptionalValidatorFactoryBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class UserDefinedDefaultValidatorConfig {
|
||||
|
||||
@Bean
|
||||
OptionalValidatorFactoryBean defaultValidator() {
|
||||
return new OptionalValidatorFactoryBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class UserDefinedJsrValidatorConfig {
|
||||
|
||||
@Bean
|
||||
Validator customValidator() {
|
||||
return mock(Validator.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class UserDefinedSpringValidatorConfig {
|
||||
|
||||
@Bean
|
||||
org.springframework.validation.Validator customValidator() {
|
||||
return mock(org.springframework.validation.Validator.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
org.springframework.validation.Validator anotherCustomValidator() {
|
||||
return mock(org.springframework.validation.Validator.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class UserDefinedPrimarySpringValidatorConfig {
|
||||
|
||||
@Bean
|
||||
org.springframework.validation.Validator customValidator() {
|
||||
return mock(org.springframework.validation.Validator.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
org.springframework.validation.Validator anotherCustomValidator() {
|
||||
return mock(org.springframework.validation.Validator.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Validated
|
||||
static class SampleService {
|
||||
|
||||
void doSomething(@Size(min = 3, max = 10) String name) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static final class ExcludedServiceConfiguration {
|
||||
|
||||
@Bean
|
||||
ExcludedService excludedService() {
|
||||
return new ExcludedService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MethodValidationExcludeFilter exclusionFilter() {
|
||||
return (type) -> type.equals(ExcludedService.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Validated
|
||||
static final class ExcludedService {
|
||||
|
||||
void doSomething(@Size(min = 3, max = 10) String name) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface AnotherSampleService {
|
||||
|
||||
void doSomething(@Min(42) Integer counter);
|
||||
|
||||
}
|
||||
|
||||
@Validated
|
||||
static class DefaultAnotherSampleService implements AnotherSampleService {
|
||||
|
||||
@Override
|
||||
public void doSomething(Integer counter) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class AnotherSampleServiceConfiguration {
|
||||
|
||||
@Bean
|
||||
AnotherSampleService anotherSampleService() {
|
||||
return new DefaultAnotherSampleService();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class SampleConfiguration {
|
||||
|
||||
@Bean
|
||||
static MethodValidationPostProcessor testMethodValidationPostProcessor() {
|
||||
return new MethodValidationPostProcessor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@org.springframework.context.annotation.Configuration(proxyBeanMethods = false)
|
||||
static class CustomValidatorConfiguration {
|
||||
|
||||
CustomValidatorConfiguration(SomeService someService) {
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
Validator customValidator() {
|
||||
return new CustomValidatorBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
static TestBeanPostProcessor testBeanPostProcessor() {
|
||||
return new TestBeanPostProcessor();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class SomeServiceConfiguration {
|
||||
|
||||
@Bean
|
||||
SomeService someService() {
|
||||
return new SomeService();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class SomeService {
|
||||
|
||||
}
|
||||
|
||||
static class TestBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
private final Set<String> postProcessed = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String name) {
|
||||
this.postProcessed.add(name);
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String name) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ConfigurationCustomizersConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
ValidationConfigurationCustomizer customizerOne() {
|
||||
return mock(ValidationConfigurationCustomizer.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
ValidationConfigurationCustomizer customizerTwo() {
|
||||
return mock(ValidationConfigurationCustomizer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure;
|
||||
|
||||
import jakarta.validation.Validator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
|
||||
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test for {@link ValidationAutoConfiguration} when Hibernate validator is present but no
|
||||
* EL implementation is available.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ClassPathExclusions({ "tomcat-embed-el-*.jar", "el-api-*.jar" })
|
||||
class ValidationAutoConfigurationWithHibernateValidatorMissingElImplTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ValidationAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void missingElDependencyIsTolerated() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).hasSingleBean(Validator.class);
|
||||
assertThat(context).hasSingleBean(MethodValidationPostProcessor.class);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure;
|
||||
|
||||
import jakarta.validation.Validator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
|
||||
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test for {@link ValidationAutoConfiguration} when no JSR-303 provider is available.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ClassPathExclusions("hibernate-validator-*.jar")
|
||||
class ValidationAutoConfigurationWithoutValidatorTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ValidationAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void validationIsDisabled() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(Validator.class);
|
||||
assertThat(context).doesNotHaveBean(MethodValidationPostProcessor.class);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.hibernate.validator.HibernateValidator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.MapBindingResult;
|
||||
import org.springframework.validation.SmartValidator;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
||||
/**
|
||||
* Tests for {@link ValidatorAdapter}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class ValidatorAdapterTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
|
||||
|
||||
@Test
|
||||
void wrapLocalValidatorFactoryBean() {
|
||||
this.contextRunner.withUserConfiguration(LocalValidatorFactoryBeanConfig.class).run((context) -> {
|
||||
ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class);
|
||||
assertThat(wrapper.supports(SampleData.class)).isTrue();
|
||||
MapBindingResult errors = new MapBindingResult(new HashMap<>(), "test");
|
||||
wrapper.validate(new SampleData(40), errors);
|
||||
assertThat(errors.getErrorCount()).isOne();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void wrapperInvokesCallbackOnNonManagedBean() {
|
||||
this.contextRunner.withUserConfiguration(NonManagedBeanConfig.class).run((context) -> {
|
||||
LocalValidatorFactoryBean validator = context.getBean(NonManagedBeanConfig.class).validator;
|
||||
then(validator).should().setApplicationContext(any(ApplicationContext.class));
|
||||
then(validator).should().afterPropertiesSet();
|
||||
then(validator).should(never()).destroy();
|
||||
context.close();
|
||||
then(validator).should().destroy();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void wrapperDoesNotInvokeCallbackOnManagedBean() {
|
||||
this.contextRunner.withUserConfiguration(ManagedBeanConfig.class).run((context) -> {
|
||||
LocalValidatorFactoryBean validator = context.getBean(ManagedBeanConfig.class).validator;
|
||||
then(validator).should(never()).setApplicationContext(any(ApplicationContext.class));
|
||||
then(validator).should(never()).afterPropertiesSet();
|
||||
then(validator).should(never()).destroy();
|
||||
context.close();
|
||||
then(validator).should(never()).destroy();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void wrapperWhenValidationProviderNotPresentShouldNotThrowException() {
|
||||
ClassPathResource hibernateValidator = new ClassPathResource(
|
||||
"META-INF/services/jakarta.validation.spi.ValidationProvider");
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(FilteredClassLoader.ClassPathResourceFilter.of(hibernateValidator),
|
||||
FilteredClassLoader.PackageFilter.of("org.hibernate.validator")))
|
||||
.run((context) -> ValidatorAdapter.get(context, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapToJakartaValidatorShouldReturnJakartaValidator() {
|
||||
this.contextRunner.withUserConfiguration(LocalValidatorFactoryBeanConfig.class).run((context) -> {
|
||||
ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class);
|
||||
assertThat(wrapper.unwrap(Validator.class)).isInstanceOf(Validator.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJakartaValidatorIsWrappedMultipleTimesUnwrapToJakartaValidatorShouldReturnJakartaValidator() {
|
||||
this.contextRunner.withUserConfiguration(DoubleWrappedConfig.class).run((context) -> {
|
||||
ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class);
|
||||
assertThat(wrapper.unwrap(Validator.class)).isInstanceOf(Validator.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapToUnsupportedTypeShouldThrow() {
|
||||
this.contextRunner.withUserConfiguration(LocalValidatorFactoryBeanConfig.class).run((context) -> {
|
||||
ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class);
|
||||
assertThatRuntimeException().isThrownBy(() -> wrapper.unwrap(HibernateValidator.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class LocalValidatorFactoryBeanConfig {
|
||||
|
||||
@Bean
|
||||
LocalValidatorFactoryBean validator() {
|
||||
return new LocalValidatorFactoryBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ValidatorAdapter wrapper(LocalValidatorFactoryBean validator) {
|
||||
return new ValidatorAdapter(validator, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class DoubleWrappedConfig {
|
||||
|
||||
@Bean
|
||||
LocalValidatorFactoryBean validator() {
|
||||
return new LocalValidatorFactoryBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ValidatorAdapter wrapper(LocalValidatorFactoryBean validator) {
|
||||
return new ValidatorAdapter(new Wrapper(validator), true);
|
||||
}
|
||||
|
||||
static class Wrapper implements SmartValidator {
|
||||
|
||||
private final SmartValidator delegate;
|
||||
|
||||
Wrapper(SmartValidator delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> type) {
|
||||
return this.delegate.supports(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
this.delegate.validate(target, errors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors, Object... validationHints) {
|
||||
this.delegate.validate(target, errors, validationHints);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T unwrap(Class<T> type) {
|
||||
if (type.isInstance(this.delegate)) {
|
||||
return (T) this.delegate;
|
||||
}
|
||||
return this.delegate.unwrap(type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class NonManagedBeanConfig {
|
||||
|
||||
private final LocalValidatorFactoryBean validator = mock(LocalValidatorFactoryBean.class);
|
||||
|
||||
@Bean
|
||||
ValidatorAdapter wrapper() {
|
||||
return new ValidatorAdapter(this.validator, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ManagedBeanConfig {
|
||||
|
||||
private final LocalValidatorFactoryBean validator = mock(LocalValidatorFactoryBean.class);
|
||||
|
||||
@Bean
|
||||
ValidatorAdapter wrapper() {
|
||||
return new ValidatorAdapter(this.validator, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class SampleData {
|
||||
|
||||
@Min(42)
|
||||
private final int counter;
|
||||
|
||||
SampleData(int counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user