diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DefaultValidatorConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DefaultValidatorConfiguration.java deleted file mode 100644 index 9311a272c6..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DefaultValidatorConfiguration.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2012-2017 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 - * - * http://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 org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.validation.MessageInterpolatorFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Role; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; - -/** - * Default validator configuration imported by {@link ValidationAutoConfiguration}. - * - * @author Stephane Nicoll - * @author Phillip Webb - */ -@Configuration -class DefaultValidatorConfiguration { - - @Bean - @ConditionalOnMissingBean(type = { "javax.validation.Validator", - "org.springframework.validation.Validator" }) - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - public static LocalValidatorFactoryBean defaultValidator() { - LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); - MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(); - factoryBean.setMessageInterpolator(interpolatorFactory.getObject()); - return factoryBean; - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java deleted file mode 100644 index e180ba284a..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2012-2017 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 - * - * http://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 org.springframework.util.Assert; -import org.springframework.validation.Errors; -import org.springframework.validation.SmartValidator; -import org.springframework.validation.Validator; -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 javax.validator.Validator} interface. - * - * @author Phillip Webb - * @since 1.5.3 - */ -public class DelegatingValidator implements SmartValidator { - - private final Validator delegate; - - /** - * Create a new {@link DelegatingValidator} instance. - * @param targetValidator the target JSR validator - */ - public DelegatingValidator(javax.validation.Validator targetValidator) { - this.delegate = new SpringValidatorAdapter(targetValidator); - } - - /** - * Create a new {@link DelegatingValidator} instance. - * @param targetValidator the target validator - */ - public DelegatingValidator(Validator targetValidator) { - Assert.notNull(targetValidator, "Target Validator must not be null"); - this.delegate = targetValidator; - } - - @Override - public boolean supports(Class clazz) { - return this.delegate.supports(clazz); - } - - @Override - public void validate(Object target, Errors errors) { - this.delegate.validate(target, errors); - } - - @Override - public void validate(Object target, Errors errors, Object... validationHints) { - if (this.delegate instanceof SmartValidator) { - ((SmartValidator) this.delegate).validate(target, errors, validationHints); - } - else { - this.delegate.validate(target, errors); - } - } - - /** - * Return the delegate validator. - * @return the delegate validator - */ - protected final Validator getDelegate() { - return this.delegate; - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/Jsr303ValidatorAdapterConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/Jsr303ValidatorAdapterConfiguration.java deleted file mode 100644 index 2573dd3e3c..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/Jsr303ValidatorAdapterConfiguration.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2012-2017 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 - * - * http://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 javax.validation.Validator; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Role; -import org.springframework.validation.SmartValidator; - -/** - * JSR 303 adapter configration imported by {@link ValidationAutoConfiguration}. - * - * @author Stephane Nicoll - * @author Phillip Webb - */ -@Configuration -class Jsr303ValidatorAdapterConfiguration { - - @Bean - @ConditionalOnSingleCandidate(Validator.class) - @ConditionalOnMissingBean(org.springframework.validation.Validator.class) - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - public SmartValidator jsr303ValidatorAdapter(Validator validator) { - return new DelegatingValidator(validator); - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/PrimaryDefaultValidatorPostProcessor.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/PrimaryDefaultValidatorPostProcessor.java new file mode 100644 index 0000000000..ad32b2c998 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/PrimaryDefaultValidatorPostProcessor.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2017 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 + * + * http://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 org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +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. + *

+ * As {@link LocalValidatorFactoryBean} exposes 3 validator related contracts and we're + * only checking for the absence {@link javax.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 + */ +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) { + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } + } + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + if (this.beanFactory == null) { + return; + } + if (!registry.containsBeanDefinition(VALIDATOR_BEAN_NAME)) { + return; + } + BeanDefinition def = registry.getBeanDefinition(VALIDATOR_BEAN_NAME); + if (def != null + && this.beanFactory.isTypeMatch(VALIDATOR_BEAN_NAME, LocalValidatorFactoryBean.class) + && def.getRole() == BeanDefinition.ROLE_INFRASTRUCTURE) { + def.setPrimary(!hasPrimarySpringValidator(registry)); + } + } + + private boolean hasPrimarySpringValidator(BeanDefinitionRegistry registry) { + String[] validatorBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + this.beanFactory, Validator.class, false, false); + for (String validatorBean : validatorBeans) { + BeanDefinition def = registry.getBeanDefinition(validatorBean); + if (def != null && def.isPrimary()) { + return true; + } + } + return false; + } + +} + diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java index 32abaccad1..7eef174b3a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java @@ -19,15 +19,18 @@ package org.springframework.boot.autoconfigure.validation; import javax.validation.Validator; import javax.validation.executable.ExecutableValidator; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 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.validation.MessageInterpolatorFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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; /** @@ -41,12 +44,20 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess @Configuration @ConditionalOnClass(ExecutableValidator.class) @ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider") -@Import({ DefaultValidatorConfiguration.class, - Jsr303ValidatorAdapterConfiguration.class }) +@Import(PrimaryDefaultValidatorPostProcessor.class) public class ValidationAutoConfiguration { @Bean - @ConditionalOnBean(Validator.class) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + @ConditionalOnMissingBean(Validator.class) + public static LocalValidatorFactoryBean defaultValidator() { + LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); + MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(); + factoryBean.setMessageInterpolator(interpolatorFactory.getObject()); + return factoryBean; + } + + @Bean @ConditionalOnMissingBean public static MethodValidationPostProcessor methodValidationPostProcessor( Environment environment, Validator validator) { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java new file mode 100644 index 0000000000..c91ee83d10 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java @@ -0,0 +1,154 @@ +/* + * Copyright 2012-2017 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 + * + * http://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 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.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 javax.validator.Validator} interface. + * + * @author Stephane Nicoll + * @author Phillip Webb + * @since 2.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 clazz) { + return this.target.supports(clazz); + } + + @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) { + ((ApplicationContextAware) this.target) + .setApplicationContext(applicationContext); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + if (!this.existingBean && this.target instanceof InitializingBean) { + ((InitializingBean) this.target).afterPropertiesSet(); + } + } + + @Override + public void destroy() throws Exception { + if (!this.existingBean && this.target instanceof DisposableBean) { + ((DisposableBean) this.target).destroy(); + } + } + + /** + * Return a {@link Validator} that only implements the {@link Validator} interface, + * wrapping it if necessary. + *

If the specified {@link Validator} is not {@code null}, it is wrapped. If not, + * a {@link javax.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(); + } + + private static Validator getExisting(ApplicationContext applicationContext) { + try { + javax.validation.Validator validator = applicationContext + .getBean(javax.validation.Validator.class); + if (validator instanceof Validator) { + return (Validator) validator; + } + return new SpringValidatorAdapter(validator); + } + catch (NoSuchBeanDefinitionException ex) { + return null; + } + } + + private static Validator create() { + OptionalValidatorFactoryBean validator = new OptionalValidatorFactoryBean(); + validator.setMessageInterpolator(new MessageInterpolatorFactory().getObject()); + return wrap(validator, false); + } + + private static Validator wrap(Validator validator, boolean existingBean) { + if (validator instanceof javax.validation.Validator) { + if (validator instanceof SpringValidatorAdapter) { + return new ValidatorAdapter((SpringValidatorAdapter) validator, + existingBean); + } + return new ValidatorAdapter( + new SpringValidatorAdapter((javax.validation.Validator) validator), + existingBean); + } + return validator; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java index 7224bea60f..262128cb46 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java @@ -23,48 +23,30 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureOrder; 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.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.validation.DelegatingValidator; +import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain; import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ConfigurationCondition; import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; -import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.core.type.AnnotationMetadata; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.http.CacheControl; -import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; import org.springframework.validation.Validator; import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration; import org.springframework.web.reactive.config.EnableWebFlux; @@ -103,7 +85,7 @@ public class WebFluxAutoConfiguration { @Configuration @EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class }) - @Import({ EnableWebFluxConfiguration.class, WebFluxValidatorRegistrar.class }) + @Import({ EnableWebFluxConfiguration.class }) public static class WebFluxConfig implements WebFluxConfigurer { private static final Log logger = LogFactory.getLog(WebFluxConfig.class); @@ -209,29 +191,17 @@ public class WebFluxAutoConfiguration { * Configuration equivalent to {@code @EnableWebFlux}. */ @Configuration - public static class EnableWebFluxConfiguration extends DelegatingWebFluxConfiguration - implements InitializingBean { + public static class EnableWebFluxConfiguration + extends DelegatingWebFluxConfiguration { - private final ApplicationContext context; - - public EnableWebFluxConfiguration(ApplicationContext context) { - this.context = context; - } - - @Bean @Override - @Conditional(DisableWebFluxValidatorCondition.class) + @Bean public Validator webFluxValidator() { - return this.context.getBean("webFluxValidator", Validator.class); - } - - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(getValidator() == null, - "Found unexpected validator configuration. A Spring Boot WebFlux " - + "validator should be registered as bean named " - + "'webFluxValidator' and not returned from " - + "WebFluxConfigurer.getValidator()"); + if (!ClassUtils.isPresent("javax.validation.Validator", + getClass().getClassLoader())) { + return super.webFluxValidator(); + } + return ValidatorAdapter.get(getApplicationContext(), getValidator()); } } @@ -297,130 +267,4 @@ public class WebFluxAutoConfiguration { } - /** - * Condition used to disable the default WebFlux validator registration. The - * {@link WebFluxValidatorRegistrar} is used to register the {@code webFluxValidator} - * bean. - */ - static class DisableWebFluxValidatorCondition implements ConfigurationCondition { - - @Override - public ConfigurationPhase getConfigurationPhase() { - return ConfigurationPhase.REGISTER_BEAN; - } - - @Override - public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - return false; - } - - } - - /** - * {@link ImportBeanDefinitionRegistrar} to deal with the WebFlux validator bean - * registration. Applies the following rules: - *

- */ - static class WebFluxValidatorRegistrar - implements ImportBeanDefinitionRegistrar, BeanFactoryAware { - - private static final String JSR303_VALIDATOR_CLASS = "javax.validation.Validator"; - - private BeanFactory beanFactory; - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, - BeanDefinitionRegistry registry) { - if (this.beanFactory instanceof ListableBeanFactory) { - registerOrAliasWebFluxValidator(registry, - (ListableBeanFactory) this.beanFactory); - } - } - - private void registerOrAliasWebFluxValidator(BeanDefinitionRegistry registry, - ListableBeanFactory beanFactory) { - String[] validatorBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( - beanFactory, Validator.class, false, false); - if (validatorBeans.length == 0) { - registerNewWebFluxValidator(registry, beanFactory); - } - else if (validatorBeans.length == 1) { - registry.registerAlias(validatorBeans[0], "webFluxValidator"); - } - else { - if (!ObjectUtils.containsElement(validatorBeans, "webFluxValidator")) { - registerNewWebFluxValidator(registry, beanFactory); - } - } - } - - private void registerNewWebFluxValidator(BeanDefinitionRegistry registry, - ListableBeanFactory beanFactory) { - RootBeanDefinition definition = new RootBeanDefinition(); - definition.setBeanClass(getClass()); - definition.setFactoryMethodName("webFluxValidator"); - registry.registerBeanDefinition("webFluxValidator", definition); - } - - static Validator webFluxValidator() { - Validator validator = new WebFluxConfigurationSupport().webFluxValidator(); - try { - if (ClassUtils.forName(JSR303_VALIDATOR_CLASS, null) - .isInstance(validator)) { - return new DelegatingWebFluxValidator(validator); - } - } - catch (Exception ex) { - } - return validator; - } - - } - - /** - * {@link DelegatingValidator} for the WebFlux validator. - */ - static class DelegatingWebFluxValidator extends DelegatingValidator - implements ApplicationContextAware, InitializingBean, DisposableBean { - - DelegatingWebFluxValidator(Validator targetValidator) { - super(targetValidator); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - if (getDelegate() instanceof ApplicationContextAware) { - ((ApplicationContextAware) getDelegate()) - .setApplicationContext(applicationContext); - } - } - - @Override - public void afterPropertiesSet() throws Exception { - if (getDelegate() instanceof InitializingBean) { - ((InitializingBean) getDelegate()).afterPropertiesSet(); - } - } - - @Override - public void destroy() throws Exception { - if (getDelegate() instanceof DisposableBean) { - ((DisposableBean) getDelegate()).destroy(); - } - } - - } - } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index 0ad98edc85..340ef81751 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -30,18 +30,11 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -52,8 +45,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; -import org.springframework.boot.autoconfigure.validation.DelegatingValidator; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; +import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain; import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy; @@ -61,31 +54,21 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter; import org.springframework.boot.web.servlet.filter.OrderedHttpPutFormContentFilter; import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ConfigurationCondition; import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.io.Resource; -import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.core.type.AnnotationMetadata; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.format.datetime.DateFormatter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.validation.DefaultMessageCodesResolver; import org.springframework.validation.MessageCodesResolver; @@ -172,7 +155,7 @@ public class WebMvcAutoConfiguration { // Defined as a nested config to ensure WebMvcConfigurer is not read when not // on the classpath @Configuration - @Import({ EnableWebMvcConfiguration.class, MvcValidatorRegistrar.class }) + @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer { @@ -380,22 +363,21 @@ public class WebMvcAutoConfiguration { * Configuration equivalent to {@code @EnableWebMvc}. */ @Configuration - public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration - implements InitializingBean { + public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration { private final WebMvcProperties mvcProperties; - private final ApplicationContext context; + private final ListableBeanFactory beanFactory; private final WebMvcRegistrations mvcRegistrations; public EnableWebMvcConfiguration( ObjectProvider mvcPropertiesProvider, ObjectProvider mvcRegistrationsProvider, - ApplicationContext context) { + ListableBeanFactory beanFactory) { this.mvcProperties = mvcPropertiesProvider.getIfAvailable(); this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique(); - this.context = context; + this.beanFactory = beanFactory; } @Bean @@ -426,9 +408,12 @@ public class WebMvcAutoConfiguration { @Bean @Override - @Conditional(DisableMvcValidatorCondition.class) public Validator mvcValidator() { - return this.context.getBean("mvcValidator", Validator.class); + if (!ClassUtils.isPresent("javax.validation.Validator", + getClass().getClassLoader())) { + return super.mvcValidator(); + } + return ValidatorAdapter.get(getApplicationContext(), getValidator()); } @Override @@ -443,7 +428,7 @@ public class WebMvcAutoConfiguration { @Override protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() { try { - return this.context.getBean(ConfigurableWebBindingInitializer.class); + return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class); } catch (NoSuchBeanDefinitionException ex) { return super.getConfigurableWebBindingInitializer(); @@ -492,15 +477,6 @@ public class WebMvcAutoConfiguration { return manager; } - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(getValidator() == null, - "Found unexpected validator configuration. A Spring Boot MVC " - + "validator should be registered as bean named " - + "'mvcValidator' and not returned from " - + "WebMvcConfigurer.getValidator()"); - } - } @Configuration @@ -628,130 +604,4 @@ public class WebMvcAutoConfiguration { } - /** - * Condition used to disable the default MVC validator registration. The - * {@link MvcValidatorRegistrar} is actually used to register the {@code mvcValidator} - * bean. - */ - static class DisableMvcValidatorCondition implements ConfigurationCondition { - - @Override - public ConfigurationPhase getConfigurationPhase() { - return ConfigurationPhase.REGISTER_BEAN; - } - - @Override - public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - return false; - } - - } - - /** - * {@link ImportBeanDefinitionRegistrar} to deal with the MVC validator bean - * registration. Applies the following rules: - * - */ - static class MvcValidatorRegistrar - implements ImportBeanDefinitionRegistrar, BeanFactoryAware { - - private static final String JSR303_VALIDATOR_CLASS = "javax.validation.Validator"; - - private BeanFactory beanFactory; - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, - BeanDefinitionRegistry registry) { - if (this.beanFactory instanceof ListableBeanFactory) { - registerOrAliasMvcValidator(registry, - (ListableBeanFactory) this.beanFactory); - } - } - - private void registerOrAliasMvcValidator(BeanDefinitionRegistry registry, - ListableBeanFactory beanFactory) { - String[] validatorBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( - beanFactory, Validator.class, false, false); - if (validatorBeans.length == 0) { - registerNewMvcValidator(registry, beanFactory); - } - else if (validatorBeans.length == 1) { - registry.registerAlias(validatorBeans[0], "mvcValidator"); - } - else { - if (!ObjectUtils.containsElement(validatorBeans, "mvcValidator")) { - registerNewMvcValidator(registry, beanFactory); - } - } - } - - private void registerNewMvcValidator(BeanDefinitionRegistry registry, - ListableBeanFactory beanFactory) { - RootBeanDefinition definition = new RootBeanDefinition(); - definition.setBeanClass(getClass()); - definition.setFactoryMethodName("mvcValidator"); - registry.registerBeanDefinition("mvcValidator", definition); - } - - static Validator mvcValidator() { - Validator validator = new WebMvcConfigurationSupport().mvcValidator(); - try { - if (ClassUtils.forName(JSR303_VALIDATOR_CLASS, null) - .isInstance(validator)) { - return new DelegatingWebMvcValidator(validator); - } - } - catch (Exception ex) { - } - return validator; - } - - } - - /** - * {@link DelegatingValidator} for the MVC validator. - */ - static class DelegatingWebMvcValidator extends DelegatingValidator - implements ApplicationContextAware, InitializingBean, DisposableBean { - - DelegatingWebMvcValidator(Validator targetValidator) { - super(targetValidator); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - if (getDelegate() instanceof ApplicationContextAware) { - ((ApplicationContextAware) getDelegate()) - .setApplicationContext(applicationContext); - } - } - - @Override - public void afterPropertiesSet() throws Exception { - if (getDelegate() instanceof InitializingBean) { - ((InitializingBean) getDelegate()).afterPropertiesSet(); - } - } - - @Override - public void destroy() throws Exception { - if (getDelegate() instanceof DisposableBean) { - ((DisposableBean) getDelegate()).destroy(); - } - } - - } - } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java index 0f68897c96..ce9b93a3c6 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java @@ -32,7 +32,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; @@ -323,9 +322,9 @@ public class SpringBootWebSecurityConfigurationTests { @Retention(RetentionPolicy.RUNTIME) @Documented @Import({ ServletWebServerFactoryAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class, - WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) + DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) protected @interface MinimalWebConfiguration { } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java deleted file mode 100644 index 04cc4e8c07..0000000000 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2012-2017 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 - * - * http://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 org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import org.springframework.validation.BeanPropertyBindingResult; -import org.springframework.validation.Errors; -import org.springframework.validation.SmartValidator; -import org.springframework.validation.Validator; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link DelegatingValidator}. - * - * @author Phillip Webb - */ -public class DelegatingValidatorTests { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Mock - private SmartValidator delegate; - - private DelegatingValidator delegating; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - this.delegating = new DelegatingValidator(this.delegate); - } - - @Test - public void createWhenJsrValidatorIsNullShouldThrowException() throws Exception { - this.thrown.expect(IllegalArgumentException.class); - this.thrown.expectMessage("Target Validator must not be null"); - new DelegatingValidator((javax.validation.Validator) null); - } - - @Test - public void createWithJsrValidatorShouldAdapt() throws Exception { - javax.validation.Validator delegate = mock(javax.validation.Validator.class); - Validator delegating = new DelegatingValidator(delegate); - Object target = new Object(); - Errors errors = new BeanPropertyBindingResult(target, "foo"); - delegating.validate(target, errors); - verify(delegate).validate(any()); - } - - @Test - public void createWithSpringValidatorWhenValidatorIsNullShouldThrowException() - throws Exception { - this.thrown.expect(IllegalArgumentException.class); - this.thrown.expectMessage("Target Validator must not be null"); - new DelegatingValidator((Validator) null); - } - - @Test - public void supportsShouldDelegateToValidator() throws Exception { - this.delegating.supports(Object.class); - verify(this.delegate).supports(Object.class); - } - - @Test - public void validateShouldDelegateToValidator() throws Exception { - Object target = new Object(); - Errors errors = new BeanPropertyBindingResult(target, "foo"); - this.delegating.validate(target, errors); - verify(this.delegate).validate(target, errors); - } - - @Test - public void validateWithHintsShouldDelegateToValidator() throws Exception { - Object target = new Object(); - Errors errors = new BeanPropertyBindingResult(target, "foo"); - Object[] hints = { "foo", "bar" }; - this.delegating.validate(target, errors, hints); - verify(this.delegate).validate(target, errors, hints); - } - - @Test - public void validateWithHintsWhenDelegateIsNotSmartShouldDelegateToSimpleValidator() - throws Exception { - Validator delegate = mock(Validator.class); - DelegatingValidator delegating = new DelegatingValidator(delegate); - Object target = new Object(); - Errors errors = new BeanPropertyBindingResult(target, "foo"); - Object[] hints = { "foo", "bar" }; - delegating.validate(target, errors, hints); - verify(delegate).validate(target, errors); - } - -} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java index df2e7e3cd9..1f83b02e4d 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java @@ -31,6 +31,7 @@ import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.validation.annotation.Validated; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; @@ -43,6 +44,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link ValidationAutoConfiguration}. * * @author Stephane Nicoll + * @author Phillip Webb */ public class ValidationAutoConfigurationTests { @@ -59,94 +61,134 @@ public class ValidationAutoConfigurationTests { } @Test - public void validationAutoConfigurationShouldConfigureJsrAndSpringValidator() - throws Exception { + public void validationAutoConfigurationShouldConfigureDefaultValidator() { load(Config.class); - Validator jsrValidator = this.context.getBean(Validator.class); String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class); - org.springframework.validation.Validator springValidator = this.context - .getBean(org.springframework.validation.Validator.class); String[] springValidatorNames = this.context .getBeanNamesForType(org.springframework.validation.Validator.class); - assertThat(jsrValidator).isInstanceOf(LocalValidatorFactoryBean.class); - assertThat(jsrValidator).isEqualTo(springValidator); assertThat(jsrValidatorNames).containsExactly("defaultValidator"); assertThat(springValidatorNames).containsExactly("defaultValidator"); + Validator jsrValidator = this.context.getBean(Validator.class); + org.springframework.validation.Validator springValidator = this.context + .getBean(org.springframework.validation.Validator.class); + assertThat(jsrValidator).isInstanceOf(LocalValidatorFactoryBean.class); + assertThat(jsrValidator).isEqualTo(springValidator); + assertThat(isPrimaryBean("defaultValidator")).isTrue(); } @Test - public void validationAutoConfigurationWhenUserProvidesValidatorShouldBackOff() - throws Exception { + public void validationAutoConfigurationWhenUserProvidesValidatorShouldBackOff() { load(UserDefinedValidatorConfig.class); - Validator jsrValidator = this.context.getBean(Validator.class); String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class); - org.springframework.validation.Validator springValidator = this.context - .getBean(org.springframework.validation.Validator.class); String[] springValidatorNames = this.context .getBeanNamesForType(org.springframework.validation.Validator.class); - assertThat(jsrValidator).isInstanceOf(OptionalValidatorFactoryBean.class); - assertThat(jsrValidator).isEqualTo(springValidator); assertThat(jsrValidatorNames).containsExactly("customValidator"); assertThat(springValidatorNames).containsExactly("customValidator"); + org.springframework.validation.Validator springValidator = this.context + .getBean(org.springframework.validation.Validator.class); + Validator jsrValidator = this.context.getBean(Validator.class); + assertThat(jsrValidator).isInstanceOf(OptionalValidatorFactoryBean.class); + assertThat(jsrValidator).isEqualTo(springValidator); + assertThat(isPrimaryBean("customValidator")).isFalse(); } @Test - public void validationAutoConfigurationWhenUserProvidesJsrOnlyShouldAdaptIt() - throws Exception { + public void validationAutoConfigurationWhenUserProvidesDefaultValidatorShouldNotEnablePrimary() { + load(UserDefinedDefaultValidatorConfig.class); + String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class); + String[] springValidatorNames = this.context + .getBeanNamesForType(org.springframework.validation.Validator.class); + assertThat(jsrValidatorNames).containsExactly("defaultValidator"); + assertThat(springValidatorNames).containsExactly("defaultValidator"); + assertThat(isPrimaryBean("defaultValidator")).isFalse(); + } + + @Test + public void validationAutoConfigurationWhenUserProvidesJsrValidatorShouldBackOff() { load(UserDefinedJsrValidatorConfig.class); - Validator jsrValidator = this.context.getBean(Validator.class); String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class); + String[] springValidatorNames = this.context + .getBeanNamesForType(org.springframework.validation.Validator.class); + assertThat(jsrValidatorNames).containsExactly("customValidator"); + assertThat(springValidatorNames).isEmpty(); + assertThat(isPrimaryBean("customValidator")).isFalse(); + } + + @Test + public void validationAutoConfigurationWhenUserProvidesSpringValidatorShouldCreateJsrValidator() { + load(UserDefinedSpringValidatorConfig.class); + String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class); + String[] springValidatorNames = this.context + .getBeanNamesForType(org.springframework.validation.Validator.class); + assertThat(jsrValidatorNames).containsExactly("defaultValidator"); + assertThat(springValidatorNames).containsExactly( + "customValidator", "anotherCustomValidator", "defaultValidator"); + Validator jsrValidator = this.context.getBean(Validator.class); org.springframework.validation.Validator springValidator = this.context .getBean(org.springframework.validation.Validator.class); + assertThat(jsrValidator).isInstanceOf(LocalValidatorFactoryBean.class); + assertThat(jsrValidator).isEqualTo(springValidator); + assertThat(isPrimaryBean("defaultValidator")).isTrue(); + } + + @Test + public void validationAutoConfigurationWhenUserProvidesPrimarySpringValidatorShouldRemovePrimaryFlag() { + load(UserDefinedPrimarySpringValidatorConfig.class); + String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class); String[] springValidatorNames = this.context .getBeanNamesForType(org.springframework.validation.Validator.class); - assertThat(jsrValidator).isNotEqualTo(springValidator); - assertThat(springValidator).isInstanceOf(DelegatingValidator.class); - assertThat(jsrValidatorNames).containsExactly("customValidator"); - assertThat(springValidatorNames).containsExactly("jsr303ValidatorAdapter"); + assertThat(jsrValidatorNames).containsExactly("defaultValidator"); + assertThat(springValidatorNames).containsExactly( + "customValidator", "anotherCustomValidator", "defaultValidator"); + Validator jsrValidator = this.context.getBean(Validator.class); + org.springframework.validation.Validator springValidator = this.context + .getBean(org.springframework.validation.Validator.class); + assertThat(jsrValidator).isInstanceOf(LocalValidatorFactoryBean.class); + assertThat(springValidator).isEqualTo( + this.context.getBean("anotherCustomValidator")); + assertThat(isPrimaryBean("defaultValidator")).isFalse(); } @Test - public void validationAutoConfigurationShouldBeEnabled() { - load(ClassWithConstraint.class); + public void validationIsEnabled() { + load(SampleService.class); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); - ClassWithConstraint service = this.context.getBean(ClassWithConstraint.class); - service.call("Valid"); + SampleService service = this.context.getBean(SampleService.class); + service.doSomething("Valid"); this.thrown.expect(ConstraintViolationException.class); - service.call("KO"); + service.doSomething("KO"); } @Test - public void validationAutoConfigurationShouldUseCglibProxy() { - load(ImplementationOfInterfaceWithConstraint.class); + public void validationUsesCglibProxy() { + load(DefaultAnotherSampleService.class); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); - ImplementationOfInterfaceWithConstraint service = this.context - .getBean(ImplementationOfInterfaceWithConstraint.class); - service.call(42); + DefaultAnotherSampleService service = this.context + .getBean(DefaultAnotherSampleService.class); + service.doSomething(42); this.thrown.expect(ConstraintViolationException.class); - service.call(2); + service.doSomething(2); } @Test - public void validationAutoConfigurationWhenProxyTargetClassIsFalseShouldUseJdkProxy() { + public void validationCanBeConfiguredToUseJdkProxy() { load(AnotherSampleServiceConfiguration.class, "spring.aop.proxy-target-class=false"); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); - assertThat(this.context - .getBeansOfType(ImplementationOfInterfaceWithConstraint.class)).isEmpty(); - InterfaceWithConstraint service = this.context - .getBean(InterfaceWithConstraint.class); - service.call(42); + assertThat(this.context.getBeansOfType(DefaultAnotherSampleService.class)) + .isEmpty(); + AnotherSampleService service = this.context.getBean(AnotherSampleService.class); + service.doSomething(42); this.thrown.expect(ConstraintViolationException.class); - service.call(2); + service.doSomething(2); } @Test - public void validationAutoConfigurationWhenUserDefinesMethodValidationPostProcessorShouldBackOff() { - load(UserDefinedMethodValidationConfig.class); + public void userDefinedMethodValidationPostProcessorTakesPrecedence() { + load(SampleConfiguration.class); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); Object userMethodValidationPostProcessor = this.context - .getBean("customMethodValidationPostProcessor"); + .getBean("testMethodValidationPostProcessor"); assertThat(this.context.getBean(MethodValidationPostProcessor.class)) .isSameAs(userMethodValidationPostProcessor); assertThat(this.context.getBeansOfType(MethodValidationPostProcessor.class)) @@ -156,7 +198,11 @@ public class ValidationAutoConfigurationTests { .getPropertyValue("validator")); } - public void load(Class config, String... environment) { + private boolean isPrimaryBean(String beanName) { + return this.context.getBeanDefinition(beanName).isPrimary(); + } + + private void load(Class config, String... environment) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(ctx, environment); if (config != null) { @@ -182,6 +228,16 @@ public class ValidationAutoConfigurationTests { } + @Configuration + static class UserDefinedDefaultValidatorConfig { + + @Bean + public OptionalValidatorFactoryBean defaultValidator() { + return new OptionalValidatorFactoryBean(); + } + + } + @Configuration static class UserDefinedJsrValidatorConfig { @@ -193,47 +249,77 @@ public class ValidationAutoConfigurationTests { } @Configuration - static class UserDefinedMethodValidationConfig { + static class UserDefinedSpringValidatorConfig { @Bean - public MethodValidationPostProcessor customMethodValidationPostProcessor() { - return new MethodValidationPostProcessor(); + public org.springframework.validation.Validator customValidator() { + return mock(org.springframework.validation.Validator.class); + } + + @Bean + public org.springframework.validation.Validator anotherCustomValidator() { + return mock(org.springframework.validation.Validator.class); } } @Configuration - static class AnotherSampleServiceConfiguration { + static class UserDefinedPrimarySpringValidatorConfig { + + @Bean + public org.springframework.validation.Validator customValidator() { + return mock(org.springframework.validation.Validator.class); + } @Bean - public InterfaceWithConstraint implementationOfInterfaceWithConstraint() { - return new ImplementationOfInterfaceWithConstraint(); + @Primary + public org.springframework.validation.Validator anotherCustomValidator() { + return mock(org.springframework.validation.Validator.class); } } @Validated - static class ClassWithConstraint { + static class SampleService { - public void call(@Size(min = 3, max = 10) String name) { + public void doSomething(@Size(min = 3, max = 10) String name) { } } - interface InterfaceWithConstraint { + interface AnotherSampleService { - void call(@Min(42) Integer counter); + void doSomething(@Min(42) Integer counter); } @Validated - static class ImplementationOfInterfaceWithConstraint - implements InterfaceWithConstraint { + static class DefaultAnotherSampleService implements AnotherSampleService { @Override - public void call(Integer counter) { + public void doSomething(Integer counter) { } } + @Configuration + static class AnotherSampleServiceConfiguration { + + @Bean + public AnotherSampleService anotherSampleService() { + return new DefaultAnotherSampleService(); + } + + } + + @Configuration + static class SampleConfiguration { + + @Bean + public MethodValidationPostProcessor testMethodValidationPostProcessor() { + return new MethodValidationPostProcessor(); + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java new file mode 100644 index 0000000000..8d6f0858bc --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java @@ -0,0 +1,152 @@ +/* + * Copyright 2012-2017 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 + * + * http://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 java.util.HashMap; + +import javax.validation.constraints.Min; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.MapBindingResult; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link ValidatorAdapter}. + * + * @author Stephane Nicoll + */ +public class ValidatorAdapterTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void wrapLocalValidatorFactoryBean() { + ValidatorAdapter wrapper = load( + LocalValidatorFactoryBeanConfig.class); + assertThat(wrapper.supports(SampleData.class)).isTrue(); + MapBindingResult errors = new MapBindingResult(new HashMap(), + "test"); + wrapper.validate(new SampleData(40), errors); + assertThat(errors.getErrorCount()).isEqualTo(1); + } + + @Test + public void wrapperInvokesCallbackOnNonManagedBean() { + load(NonManagedBeanConfig.class); + LocalValidatorFactoryBean validator = this.context + .getBean(NonManagedBeanConfig.class).validator; + verify(validator, times(1)).setApplicationContext(any(ApplicationContext.class)); + verify(validator, times(1)).afterPropertiesSet(); + verify(validator, times(0)).destroy(); + this.context.close(); + this.context = null; + verify(validator, times(1)).destroy(); + } + + @Test + public void wrapperDoesNotInvokeCallbackOnManagedBean() { + load(ManagedBeanConfig.class); + LocalValidatorFactoryBean validator = this.context + .getBean(ManagedBeanConfig.class).validator; + verify(validator, times(0)).setApplicationContext(any(ApplicationContext.class)); + verify(validator, times(0)).afterPropertiesSet(); + verify(validator, times(0)).destroy(); + this.context.close(); + this.context = null; + verify(validator, times(0)).destroy(); + } + + private ValidatorAdapter load(Class config) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(config); + ctx.refresh(); + this.context = ctx; + return this.context.getBean(ValidatorAdapter.class); + } + + @Configuration + static class LocalValidatorFactoryBeanConfig { + + @Bean + public LocalValidatorFactoryBean validator() { + return new LocalValidatorFactoryBean(); + } + + @Bean + public ValidatorAdapter wrapper() { + return new ValidatorAdapter(validator(), true); + } + + } + + @Configuration + static class NonManagedBeanConfig { + + private final LocalValidatorFactoryBean validator = mock( + LocalValidatorFactoryBean.class); + + @Bean + public ValidatorAdapter wrapper() { + return new ValidatorAdapter(this.validator, false); + } + + } + + @Configuration + static class ManagedBeanConfig { + + private final LocalValidatorFactoryBean validator = mock( + LocalValidatorFactoryBean.class); + + @Bean + public ValidatorAdapter wrapper() { + return new ValidatorAdapter(this.validator, true); + } + + } + + static class SampleData { + + @Min(42) + private int counter; + + SampleData(int counter) { + this.counter = counter; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java index 70ba22a0f8..bddf089f29 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java @@ -21,13 +21,15 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import javax.validation.ValidatorFactory; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.boot.autoconfigure.validation.DelegatingValidator; +import org.springframework.beans.DirectFieldAccessor; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; +import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationTests.Config; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.util.EnvironmentTestUtils; @@ -172,134 +174,107 @@ public class WebFluxAutoConfigurationTests { } @Test - public void validatorWhenSuppliedByConfigurerShouldThrowException() throws Exception { - this.thrown.expect(BeanCreationException.class); - this.thrown.expectMessage("unexpected validator configuration"); - load(ValidatorWebFluxConfigurer.class); + public void validatorWhenNoValidatorShouldUseDefault() { + load(null, new Class[] { ValidationAutoConfiguration.class }); + assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); + assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) + .isEmpty(); + String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); + assertThat(springValidatorBeans).containsExactly("webFluxValidator"); } @Test - public void validatorWhenAutoConfiguredShouldUseAlias() throws Exception { + public void validatorWhenNoCustomizationShouldUseAutoConfigured() { load(); - Object defaultValidator = this.context.getBean("defaultValidator"); - Object webFluxValidator = this.context.getBean("webFluxValidator"); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(webFluxValidator).isSameAs(defaultValidator); - assertThat(springValidatorBeans).containsExactly("defaultValidator"); assertThat(jsrValidatorBeans).containsExactly("defaultValidator"); + assertThat(springValidatorBeans).containsExactly("defaultValidator", "webFluxValidator"); + Validator validator = this.context.getBean("webFluxValidator", Validator.class); + assertThat(validator).isInstanceOf(ValidatorAdapter.class); + Object defaultValidator = this.context.getBean("defaultValidator"); + assertThat(((ValidatorAdapter) validator).getTarget()).isSameAs(defaultValidator); + // Primary Spring validator is the one use by WebFlux behind the scenes + assertThat(this.context.getBean(Validator.class)).isEqualTo(defaultValidator); } @Test - public void validatorWhenUserDefinedSpringOnlyShouldUseDefined() throws Exception { - load(UserDefinedSpringOnlyValidator.class); - Object customValidator = this.context.getBean("customValidator"); - Object webFluxValidator = this.context.getBean("webFluxValidator"); - String[] jsrValidatorBeans = this.context - .getBeanNamesForType(javax.validation.Validator.class); - String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(webFluxValidator).isSameAs(customValidator); - assertThat(this.context.getBean(Validator.class)).isEqualTo(customValidator); - assertThat(springValidatorBeans).containsExactly("customValidator"); - assertThat(jsrValidatorBeans).isEmpty(); - } - - @Test - public void validatorWhenUserDefinedJsr303ShouldAdapt() throws Exception { - load(UserDefinedJsr303Validator.class); - Object customValidator = this.context.getBean("customValidator"); - Object webFluxValidator = this.context.getBean("webFluxValidator"); - String[] jsrValidatorBeans = this.context - .getBeanNamesForType(javax.validation.Validator.class); + public void validatorWithConfigurerShouldUseSpringValidator() { + load(ValidatorWebFluxConfigurer.class, + new Class[] { ValidationAutoConfiguration.class }); + assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); + assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) + .isEmpty(); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(webFluxValidator).isNotSameAs(customValidator); - assertThat(this.context.getBean(javax.validation.Validator.class)) - .isEqualTo(customValidator); - assertThat(springValidatorBeans).containsExactly("jsr303ValidatorAdapter"); - assertThat(jsrValidatorBeans).containsExactly("customValidator"); + assertThat(springValidatorBeans).containsExactly("webFluxValidator"); + assertThat(this.context.getBean("webFluxValidator")) + .isSameAs(this.context.getBean(ValidatorWebFluxConfigurer.class).validator); } @Test - public void validatorWhenUserDefinedSingleJsr303AndSpringShouldUseDefined() - throws Exception { - load(UserDefinedSingleJsr303AndSpringValidator.class); - Object customValidator = this.context.getBean("customValidator"); - Object webFluxValidator = this.context.getBean("webFluxValidator"); - String[] jsrValidatorBeans = this.context - .getBeanNamesForType(javax.validation.Validator.class); + public void validatorWithConfigurerDoesNotExposeJsr303() { + load(ValidatorJsr303WebFluxConfigurer.class, new Class[] { ValidationAutoConfiguration.class }); + assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); + assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) + .isEmpty(); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(webFluxValidator).isSameAs(customValidator); - assertThat(this.context.getBean(javax.validation.Validator.class)) - .isEqualTo(customValidator); - assertThat(this.context.getBean(Validator.class)).isEqualTo(customValidator); - assertThat(springValidatorBeans).containsExactly("customValidator"); - assertThat(jsrValidatorBeans).containsExactly("customValidator"); + assertThat(springValidatorBeans).containsExactly("webFluxValidator"); + Validator validator = this.context.getBean("webFluxValidator", Validator.class); + assertThat(validator).isInstanceOf(ValidatorAdapter.class); + assertThat(((ValidatorAdapter) validator).getTarget()) + .isSameAs(this.context.getBean(ValidatorJsr303WebFluxConfigurer.class).validator); } @Test - public void validatorWhenUserDefinedJsr303AndSpringShouldUseDefined() - throws Exception { - load(UserDefinedJsr303AndSpringValidator.class); - Object customJsrValidator = this.context.getBean("customJsrValidator"); - Object customSpringValidator = this.context.getBean("customSpringValidator"); - Object webFluxValidator = this.context.getBean("webFluxValidator"); - String[] jsrValidatorBeans = this.context - .getBeanNamesForType(javax.validation.Validator.class); + public void validationCustomConfigurerTakesPrecedence() { + load(ValidatorWebFluxConfigurer.class); + assertThat(this.context.getBeansOfType(ValidatorFactory.class)).hasSize(1); + assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) + .hasSize(1); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(customJsrValidator).isNotSameAs(customSpringValidator); - assertThat(webFluxValidator).isSameAs(customSpringValidator); - assertThat(this.context.getBean(javax.validation.Validator.class)) - .isEqualTo(customJsrValidator); + assertThat(springValidatorBeans) + .containsExactly("defaultValidator", "webFluxValidator"); + assertThat(this.context.getBean("webFluxValidator")) + .isSameAs(this.context.getBean(ValidatorWebFluxConfigurer.class).validator); + // Primary Spring validator is the auto-configured one as the WebFlux one has been + // customized via a WebFluxConfigurer assertThat(this.context.getBean(Validator.class)) - .isEqualTo(customSpringValidator); - assertThat(springValidatorBeans).containsExactly("customSpringValidator"); - assertThat(jsrValidatorBeans).containsExactly("customJsrValidator"); - } - - @Test - public void validatorWhenExcludingValidatorAutoConfigurationShouldUseWebFlux() - throws Exception { - load(null, new Class[] { ValidationAutoConfiguration.class }); - Object webFluxValidator = this.context.getBean("webFluxValidator"); - String[] jsrValidatorBeans = this.context - .getBeanNamesForType(javax.validation.Validator.class); - String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(webFluxValidator).isInstanceOf(DelegatingValidator.class); - assertThat(springValidatorBeans).containsExactly("webFluxValidator"); - assertThat(jsrValidatorBeans).isEmpty(); + .isEqualTo(this.context.getBean("defaultValidator")); } @Test - public void validatorWhenMultipleValidatorsAndNoWebFluxValidatorShouldAddWebFlux() - throws Exception { - load(MultipleValidatorsAndNoWebFluxValidator.class); - Object customValidator1 = this.context.getBean("customValidator1"); - Object customValidator2 = this.context.getBean("customValidator2"); - Object webFluxValidator = this.context.getBean("webFluxValidator"); + public void validatorWithCustomSpringValidatorIgnored() { + load(CustomSpringValidator.class); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(webFluxValidator).isNotSameAs(customValidator1) - .isNotSameAs(customValidator2); - assertThat(springValidatorBeans).containsExactly("customValidator1", - "customValidator2", "webFluxValidator"); - assertThat(jsrValidatorBeans).isEmpty(); + assertThat(jsrValidatorBeans).containsExactly("defaultValidator"); + assertThat(springValidatorBeans).containsExactly( + "customValidator", "defaultValidator", "webFluxValidator"); + Validator validator = this.context.getBean("webFluxValidator", Validator.class); + assertThat(validator).isInstanceOf(ValidatorAdapter.class); + Object defaultValidator = this.context.getBean("defaultValidator"); + assertThat(((ValidatorAdapter) validator).getTarget()) + .isSameAs(defaultValidator); + // Primary Spring validator is the one use by WebFlux behind the scenes + assertThat(this.context.getBean(Validator.class)).isEqualTo(defaultValidator); } @Test - public void validatorWhenMultipleValidatorsAndWebFluxValidatorShouldUseWebFlux() - throws Exception { - load(MultipleValidatorsAndWebFluxValidator.class); - Object customValidator = this.context.getBean("customValidator"); - Object webFluxValidator = this.context.getBean("webFluxValidator"); + public void validatorWithCustomJsr303ValidatorExposedAsSpringValidator() { + load(CustomJsr303Validator.class); + assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(webFluxValidator).isNotSameAs(customValidator); - assertThat(springValidatorBeans).containsExactly("customValidator", - "webFluxValidator"); - assertThat(jsrValidatorBeans).isEmpty(); + assertThat(jsrValidatorBeans).containsExactly("customValidator"); + assertThat(springValidatorBeans).containsExactly("webFluxValidator"); + Validator validator = this.context.getBean(Validator.class); + assertThat(validator).isInstanceOf(ValidatorAdapter.class); + Validator target = ((ValidatorAdapter) validator).getTarget(); + assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator")) + .isSameAs(this.context.getBean("customValidator")); } private void load(String... environment) { @@ -324,7 +299,6 @@ public class WebFluxAutoConfigurationTests { } this.context.register(configClasses.toArray(new Class[configClasses.size()])); this.context.refresh(); - } @Configuration @@ -381,86 +355,45 @@ public class WebFluxAutoConfigurationTests { @Configuration protected static class ValidatorWebFluxConfigurer implements WebFluxConfigurer { + private final Validator validator = mock(Validator.class); + @Override public Optional getValidator() { - return Optional.of(mock(Validator.class)); + return Optional.of(this.validator); } } @Configuration - static class UserDefinedSpringOnlyValidator { + protected static class ValidatorJsr303WebFluxConfigurer implements WebFluxConfigurer { - @Bean - public Validator customValidator() { - return mock(Validator.class); - } - - } - - @Configuration - static class UserDefinedJsr303Validator { - - @Bean - public javax.validation.Validator customValidator() { - return mock(javax.validation.Validator.class); - } + private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); - } - - @Configuration - static class UserDefinedSingleJsr303AndSpringValidator { - - @Bean - public LocalValidatorFactoryBean customValidator() { - return new LocalValidatorFactoryBean(); + @Override + public Optional getValidator() { + return Optional.of(this.validator); } } @Configuration - static class UserDefinedJsr303AndSpringValidator { + static class CustomJsr303Validator { @Bean - public javax.validation.Validator customJsrValidator() { + public javax.validation.Validator customValidator() { return mock(javax.validation.Validator.class); } - @Bean - public Validator customSpringValidator() { - return mock(Validator.class); - } - } @Configuration - static class MultipleValidatorsAndNoWebFluxValidator { - - @Bean - public Validator customValidator1() { - return mock(Validator.class); - } - - @Bean - public Validator customValidator2() { - return mock(Validator.class); - } - - } - - @Configuration - static class MultipleValidatorsAndWebFluxValidator { + static class CustomSpringValidator { @Bean public Validator customValidator() { return mock(Validator.class); } - @Bean - public Validator webFluxValidator() { - return mock(Validator.class); - } - } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index 206650331a..87f9df050f 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -27,6 +27,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.validation.ValidatorFactory; import org.assertj.core.api.Condition; import org.joda.time.DateTime; @@ -36,13 +37,12 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.validation.DelegatingValidator; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; +import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WelcomePageHandlerMapping; import org.springframework.boot.test.util.EnvironmentTestUtils; @@ -659,134 +659,105 @@ public class WebMvcAutoConfigurationTests { } @Test - public void validatorWhenSuppliedByConfigurerShouldThrowException() throws Exception { - this.thrown.expect(BeanCreationException.class); - this.thrown.expectMessage("unexpected validator configuration"); - load(ValidatorWebMvcConfigurer.class); + public void validatorWhenNoValidatorShouldUseDefault() { + load(null, new Class[] { ValidationAutoConfiguration.class }); + assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); + assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) + .isEmpty(); + String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); + assertThat(springValidatorBeans).containsExactly("mvcValidator"); } @Test - public void validatorWhenAutoConfiguredShouldUseAlias() throws Exception { + public void validatorWhenNoCustomizationShouldUseAutoConfigured() { load(); - Object defaultValidator = this.context.getBean("defaultValidator"); - Object mvcValidator = this.context.getBean("mvcValidator"); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(mvcValidator).isSameAs(defaultValidator); - assertThat(springValidatorBeans).containsExactly("defaultValidator"); assertThat(jsrValidatorBeans).containsExactly("defaultValidator"); + assertThat(springValidatorBeans).containsExactly("defaultValidator", "mvcValidator"); + Validator validator = this.context.getBean("mvcValidator", Validator.class); + assertThat(validator).isInstanceOf(ValidatorAdapter.class); + Object defaultValidator = this.context.getBean("defaultValidator"); + assertThat(((ValidatorAdapter) validator).getTarget()).isSameAs(defaultValidator); + // Primary Spring validator is the one use by MVC behind the scenes + assertThat(this.context.getBean(Validator.class)).isEqualTo(defaultValidator); } @Test - public void validatorWhenUserDefinedSpringOnlyShouldUseDefined() throws Exception { - load(UserDefinedSpringOnlyValidator.class); - Object customValidator = this.context.getBean("customValidator"); - Object mvcValidator = this.context.getBean("mvcValidator"); - String[] jsrValidatorBeans = this.context - .getBeanNamesForType(javax.validation.Validator.class); + public void validatorWithConfigurerShouldUseSpringValidator() { + load(MvcValidator.class, new Class[] { ValidationAutoConfiguration.class }); + assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); + assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) + .isEmpty(); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(mvcValidator).isSameAs(customValidator); - assertThat(this.context.getBean(Validator.class)).isEqualTo(customValidator); - assertThat(springValidatorBeans).containsExactly("customValidator"); - assertThat(jsrValidatorBeans).isEmpty(); - } - - @Test - public void validatorWhenUserDefinedJsr303ShouldAdapt() throws Exception { - load(UserDefinedJsr303Validator.class); - Object customValidator = this.context.getBean("customValidator"); - Object mvcValidator = this.context.getBean("mvcValidator"); - String[] jsrValidatorBeans = this.context - .getBeanNamesForType(javax.validation.Validator.class); - String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(mvcValidator).isNotSameAs(customValidator); - assertThat(this.context.getBean(javax.validation.Validator.class)) - .isEqualTo(customValidator); - assertThat(springValidatorBeans).containsExactly("jsr303ValidatorAdapter"); - assertThat(jsrValidatorBeans).containsExactly("customValidator"); + assertThat(springValidatorBeans).containsExactly("mvcValidator"); + assertThat(this.context.getBean("mvcValidator")) + .isSameAs(this.context.getBean(MvcValidator.class).validator); } @Test - public void validatorWhenUserDefinedSingleJsr303AndSpringShouldUseDefined() - throws Exception { - load(UserDefinedSingleJsr303AndSpringValidator.class); - Object customValidator = this.context.getBean("customValidator"); - Object mvcValidator = this.context.getBean("mvcValidator"); - String[] jsrValidatorBeans = this.context - .getBeanNamesForType(javax.validation.Validator.class); + public void validatorWithConfigurerDoesNotExposeJsr303() { + load(MvcJsr303Validator.class, new Class[] { ValidationAutoConfiguration.class }); + assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); + assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) + .isEmpty(); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(mvcValidator).isSameAs(customValidator); - assertThat(this.context.getBean(javax.validation.Validator.class)) - .isEqualTo(customValidator); - assertThat(this.context.getBean(Validator.class)).isEqualTo(customValidator); - assertThat(springValidatorBeans).containsExactly("customValidator"); - assertThat(jsrValidatorBeans).containsExactly("customValidator"); + assertThat(springValidatorBeans).containsExactly("mvcValidator"); + Validator validator = this.context.getBean("mvcValidator", Validator.class); + assertThat(validator).isInstanceOf(ValidatorAdapter.class); + assertThat(((ValidatorAdapter) validator).getTarget()) + .isSameAs(this.context.getBean(MvcJsr303Validator.class).validator); } @Test - public void validatorWhenUserDefinedJsr303AndSpringShouldUseDefined() - throws Exception { - load(UserDefinedJsr303AndSpringValidator.class); - Object customJsrValidator = this.context.getBean("customJsrValidator"); - Object customSpringValidator = this.context.getBean("customSpringValidator"); - Object mvcValidator = this.context.getBean("mvcValidator"); - String[] jsrValidatorBeans = this.context - .getBeanNamesForType(javax.validation.Validator.class); + public void validatorWithConfigurerTakesPrecedence() { + load(MvcValidator.class); + assertThat(this.context.getBeansOfType(ValidatorFactory.class)).hasSize(1); + assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) + .hasSize(1); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(customJsrValidator).isNotSameAs(customSpringValidator); - assertThat(mvcValidator).isSameAs(customSpringValidator); - assertThat(this.context.getBean(javax.validation.Validator.class)) - .isEqualTo(customJsrValidator); + assertThat(springValidatorBeans).containsExactly("defaultValidator", "mvcValidator"); + assertThat(this.context.getBean("mvcValidator")) + .isSameAs(this.context.getBean(MvcValidator.class).validator); + // Primary Spring validator is the auto-configured one as the MVC one has been + // customized via a WebMvcConfigurer assertThat(this.context.getBean(Validator.class)) - .isEqualTo(customSpringValidator); - assertThat(springValidatorBeans).containsExactly("customSpringValidator"); - assertThat(jsrValidatorBeans).containsExactly("customJsrValidator"); + .isEqualTo(this.context.getBean("defaultValidator")); } @Test - public void validatorWhenExcludingValidatorAutoConfigurationShouldUseMvc() - throws Exception { - load(null, new Class[] { ValidationAutoConfiguration.class }); - Object mvcValidator = this.context.getBean("mvcValidator"); + public void validatorWithCustomSpringValidatorIgnored() { + load(CustomSpringValidator.class); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(mvcValidator).isInstanceOf(DelegatingValidator.class); - assertThat(springValidatorBeans).containsExactly("mvcValidator"); - assertThat(jsrValidatorBeans).isEmpty(); - } - - @Test - public void validatorWhenMultipleValidatorsAndNoMvcValidatorShouldAddMvc() - throws Exception { - load(MultipleValidatorsAndNoMvcValidator.class); - Object customValidator1 = this.context.getBean("customValidator1"); - Object customValidator2 = this.context.getBean("customValidator2"); - Object mvcValidator = this.context.getBean("mvcValidator"); - String[] jsrValidatorBeans = this.context - .getBeanNamesForType(javax.validation.Validator.class); - String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(mvcValidator).isNotSameAs(customValidator1) - .isNotSameAs(customValidator2); - assertThat(springValidatorBeans).containsExactly("customValidator1", - "customValidator2", "mvcValidator"); - assertThat(jsrValidatorBeans).isEmpty(); + assertThat(jsrValidatorBeans).containsExactly("defaultValidator"); + assertThat(springValidatorBeans).containsExactly( + "customSpringValidator", "defaultValidator", "mvcValidator"); + Validator validator = this.context.getBean("mvcValidator", Validator.class); + assertThat(validator).isInstanceOf(ValidatorAdapter.class); + Object defaultValidator = this.context.getBean("defaultValidator"); + assertThat(((ValidatorAdapter) validator).getTarget()) + .isSameAs(defaultValidator); + // Primary Spring validator is the one use by MVC behind the scenes + assertThat(this.context.getBean(Validator.class)).isEqualTo(defaultValidator); } @Test - public void validatorWhenMultipleValidatorsAndMvcValidatorShouldUseMvc() - throws Exception { - load(MultipleValidatorsAndMvcValidator.class); - Object customValidator = this.context.getBean("customValidator"); - Object mvcValidator = this.context.getBean("mvcValidator"); + public void validatorWithCustomJsr303ValidatorExposedAsSpringValidator() { + load(CustomJsr303Validator.class); + assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(mvcValidator).isNotSameAs(customValidator); - assertThat(springValidatorBeans).containsExactly("customValidator", - "mvcValidator"); - assertThat(jsrValidatorBeans).isEmpty(); + assertThat(jsrValidatorBeans).containsExactly("customJsr303Validator"); + assertThat(springValidatorBeans).containsExactly("mvcValidator"); + Validator validator = this.context.getBean(Validator.class); + assertThat(validator).isInstanceOf(ValidatorAdapter.class); + Validator target = ((ValidatorAdapter) validator).getTarget(); + assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator")) + .isSameAs(this.context.getBean("customJsr303Validator")); } private void load(Class config, String... environment) { @@ -976,85 +947,44 @@ public class WebMvcAutoConfigurationTests { } @Configuration - protected static class ValidatorWebMvcConfigurer implements WebMvcConfigurer { + protected static class MvcValidator implements WebMvcConfigurer { + + private final Validator validator = mock(Validator.class); @Override public Validator getValidator() { - return mock(Validator.class); - } - - } - - @Configuration - static class UserDefinedSpringOnlyValidator { - - @Bean - public Validator customValidator() { - return mock(Validator.class); + return this.validator; } } @Configuration - static class UserDefinedJsr303Validator { + protected static class MvcJsr303Validator implements WebMvcConfigurer { - @Bean - public javax.validation.Validator customValidator() { - return mock(javax.validation.Validator.class); - } + private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); - } - - @Configuration - static class UserDefinedSingleJsr303AndSpringValidator { - - @Bean - public LocalValidatorFactoryBean customValidator() { - return new LocalValidatorFactoryBean(); + @Override + public Validator getValidator() { + return this.validator; } } @Configuration - static class UserDefinedJsr303AndSpringValidator { + static class CustomJsr303Validator { @Bean - public javax.validation.Validator customJsrValidator() { + public javax.validation.Validator customJsr303Validator() { return mock(javax.validation.Validator.class); } - @Bean - public Validator customSpringValidator() { - return mock(Validator.class); - } - } @Configuration - static class MultipleValidatorsAndNoMvcValidator { - - @Bean - public Validator customValidator1() { - return mock(Validator.class); - } + static class CustomSpringValidator { @Bean - public Validator customValidator2() { - return mock(Validator.class); - } - - } - - @Configuration - static class MultipleValidatorsAndMvcValidator { - - @Bean - public Validator customValidator() { - return mock(Validator.class); - } - - @Bean - public Validator mvcValidator() { + public Validator customSpringValidator() { return mock(Validator.class); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerDirectMockMvcTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerDirectMockMvcTests.java index ce092308ec..255d07e773 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerDirectMockMvcTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerDirectMockMvcTests.java @@ -36,7 +36,6 @@ import org.junit.rules.ExpectedException; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; @@ -130,9 +129,9 @@ public class BasicErrorControllerDirectMockMvcTests { @Retention(RetentionPolicy.RUNTIME) @Documented @Import({ ServletWebServerFactoryAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class, - WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) + DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) protected @interface MinimalWebConfiguration { } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java index 45e451a9a5..881dc9cf32 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerMockMvcTests.java @@ -36,7 +36,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; @@ -132,9 +131,9 @@ public class BasicErrorControllerMockMvcTests { @Documented @Import({ ServletWebServerFactoryAutoConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class, - WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) + DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) private @interface MinimalWebConfiguration { }