diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java
index 95b3b5b5bca..cf56833f016 100755
--- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java
@@ -42,6 +42,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+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;
@@ -85,15 +86,16 @@ public class EndpointMvcIntegrationTests {
@Test
public void envEndpointNotHidden() throws InterruptedException {
String body = new TestRestTemplate().getForObject(
- "http://localhost:" + this.port + "/application/env/foo.bar", String.class);
+ "http://localhost:" + this.port + "/application/env/foo.bar",
+ String.class);
assertThat(body).isNotNull().contains("\"baz\"");
assertThat(this.interceptor.invoked()).isTrue();
}
@Test
public void healthEndpointNotHidden() throws InterruptedException {
- String body = new TestRestTemplate()
- .getForObject("http://localhost:" + this.port + "/application/health", String.class);
+ String body = new TestRestTemplate().getForObject(
+ "http://localhost:" + this.port + "/application/health", String.class);
assertThat(body).isNotNull().contains("status");
assertThat(this.interceptor.invoked()).isTrue();
}
@@ -153,9 +155,9 @@ public class EndpointMvcIntegrationTests {
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.class,
- DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
- JacksonAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
- PropertyPlaceholderAutoConfiguration.class })
+ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
+ WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
+ ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected @interface MinimalWebConfiguration {
}
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
new file mode 100644
index 00000000000..9311a272c6f
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DefaultValidatorConfiguration.java
@@ -0,0 +1,47 @@
+/*
+ * 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
new file mode 100644
index 00000000000..e180ba284a4
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java
@@ -0,0 +1,82 @@
+/*
+ * 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
new file mode 100644
index 00000000000..2573dd3e3cf
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/Jsr303ValidatorAdapterConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * 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/SpringValidator.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/SpringValidator.java
deleted file mode 100644
index 75dcf08562b..00000000000
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/SpringValidator.java
+++ /dev/null
@@ -1,145 +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.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;
-
-/**
- * A {@link SmartValidator} exposed as a bean for WebMvc use. Wraps existing
- * {@link SpringValidatorAdapter} instances so that only the Spring's {@link Validator}
- * type is exposed. This prevents such a bean to expose both the Spring and JSR-303
- * validator contract at the same time.
- *
- * @author Stephane Nicoll
- * @author Phillip Webb
- * @since 2.0.0
- */
-public class SpringValidator implements SmartValidator, ApplicationContextAware,
- InitializingBean, DisposableBean {
-
- private final SpringValidatorAdapter target;
-
- private final boolean existingBean;
-
- public SpringValidator(SpringValidatorAdapter target, boolean existingBean) {
- this.target = target;
- this.existingBean = existingBean;
- }
-
- public final SpringValidatorAdapter 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();
- }
- }
-
- 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 SpringValidator((SpringValidatorAdapter) validator,
- existingBean);
- }
- return new SpringValidator(
- new SpringValidatorAdapter((javax.validation.Validator) validator),
- existingBean);
- }
- return validator;
- }
-
-}
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 9feba19dad6..99c4efa6b9b 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,18 +19,16 @@ 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.bind.RelaxedPropertyResolver;
-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.context.annotation.Import;
import org.springframework.core.env.Environment;
-import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
/**
@@ -43,19 +41,12 @@ 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 })
public class ValidationAutoConfiguration {
@Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- @ConditionalOnMissingBean
- public static Validator jsr303Validator() {
- LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
- MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
- factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
- return factoryBean;
- }
-
- @Bean
+ @ConditionalOnBean(Validator.class)
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(
Environment environment, Validator validator) {
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAnnotationAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAnnotationAutoConfiguration.java
index 90e16045380..f690ccf66a7 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAnnotationAutoConfiguration.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAnnotationAutoConfiguration.java
@@ -23,30 +23,50 @@ 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.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.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
+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.SpringValidator;
+import org.springframework.boot.autoconfigure.validation.DelegatingValidator;
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.Role;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
+import org.springframework.core.type.AnnotatedTypeMetadata;
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;
@@ -82,6 +102,12 @@ import org.springframework.web.reactive.result.view.ViewResolver;
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxAnnotationAutoConfiguration {
+ @Bean
+ @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ public static WebFluxValidatorPostProcessor mvcValidatorAliasPostProcessor() {
+ return new WebFluxValidatorPostProcessor();
+ }
+
@Configuration
@EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
@Import(EnableWebFluxConfiguration.class)
@@ -190,17 +216,29 @@ public class WebFluxAnnotationAutoConfiguration {
* Configuration equivalent to {@code @EnableWebFlux}.
*/
@Configuration
- public static class EnableWebFluxConfiguration
- extends DelegatingWebFluxConfiguration {
+ public static class EnableWebFluxConfiguration extends DelegatingWebFluxConfiguration
+ implements InitializingBean {
+
+ private final ApplicationContext context;
+
+ public EnableWebFluxConfiguration(ApplicationContext context) {
+ this.context = context;
+ }
+
+ @Bean
+ @Override
+ @Conditional(DisableWebFluxValidatorCondition.class)
+ public Validator webFluxValidator() {
+ return this.context.getBean("webFluxValidator", Validator.class);
+ }
@Override
- @Bean
- public Validator webFluxValidator() {
- if (!ClassUtils.isPresent("javax.validation.Validator",
- getClass().getClassLoader())) {
- return super.webFluxValidator();
- }
- return SpringValidator.get(getApplicationContext(), getValidator());
+ 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()");
}
}
@@ -266,4 +304,128 @@ public class WebFluxAnnotationAutoConfiguration {
}
+ /**
+ * Condition used to disable the default WebFlux validator registration. The
+ * {@link WebFluxValidatorPostProcessor} is used to configure 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 BeanFactoryPostProcessor} to deal with the MVC validator bean registration.
+ * Applies the following rules:
+ *
+ * - With no validators - Uses standard
+ * {@link WebFluxConfigurationSupport#webFluxValidator()} logic.
+ * - With a single validator - Uses an alias.
+ * - With multiple validators - Registers a mvcValidator bean if not already
+ * defined.
+ *
+ */
+ @Order(Ordered.LOWEST_PRECEDENCE)
+ static class WebFluxValidatorPostProcessor
+ implements BeanDefinitionRegistryPostProcessor {
+
+ private static final String JSR303_VALIDATOR_CLASS = "javax.validation.Validator";
+
+ @Override
+ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
+ throws BeansException {
+ if (registry instanceof ListableBeanFactory) {
+ postProcess(registry, (ListableBeanFactory) registry);
+ }
+ }
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
+ throws BeansException {
+ }
+
+ private void postProcess(BeanDefinitionRegistry registry,
+ ListableBeanFactory beanFactory) {
+ String[] validatorBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
+ beanFactory, Validator.class, false, false);
+ if (validatorBeans.length == 0) {
+ registerMvcValidator(registry, beanFactory);
+ }
+ else if (validatorBeans.length == 1) {
+ registry.registerAlias(validatorBeans[0], "webFluxValidator");
+ }
+ else {
+ if (!ObjectUtils.containsElement(validatorBeans, "webFluxValidator")) {
+ registerMvcValidator(registry, beanFactory);
+ }
+ }
+ }
+
+ private void registerMvcValidator(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 6399bdccce9..a9e0f49db66 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,11 +30,21 @@ 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.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.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
+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;
@@ -45,7 +55,7 @@ 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.SpringValidator;
+import org.springframework.boot.autoconfigure.validation.DelegatingValidator;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
@@ -54,21 +64,31 @@ 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.Primary;
+import org.springframework.context.annotation.Role;
import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
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.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;
@@ -147,6 +167,12 @@ public class WebMvcAutoConfiguration {
public static final String SKIP_PATH_EXTENSION_CONTENT_NEGOTIATION_ATTRIBUTE = PathExtensionContentNegotiationStrategy.class
.getName() + ".SKIP";
+ @Bean
+ @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ public static MvcValidatorPostProcessor mvcValidatorAliasPostProcessor() {
+ return new MvcValidatorPostProcessor();
+ }
+
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
@@ -372,21 +398,22 @@ public class WebMvcAutoConfiguration {
* Configuration equivalent to {@code @EnableWebMvc}.
*/
@Configuration
- public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
+ public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration
+ implements InitializingBean {
private final WebMvcProperties mvcProperties;
- private final ListableBeanFactory beanFactory;
+ private final ApplicationContext context;
private final WebMvcRegistrations mvcRegistrations;
public EnableWebMvcConfiguration(
ObjectProvider mvcPropertiesProvider,
ObjectProvider mvcRegistrationsProvider,
- ListableBeanFactory beanFactory) {
+ ApplicationContext context) {
this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
- this.beanFactory = beanFactory;
+ this.context = context;
}
@Bean
@@ -417,12 +444,9 @@ public class WebMvcAutoConfiguration {
@Bean
@Override
+ @Conditional(DisableMvcValidatorCondition.class)
public Validator mvcValidator() {
- if (!ClassUtils.isPresent("javax.validation.Validator",
- getClass().getClassLoader())) {
- return super.mvcValidator();
- }
- return SpringValidator.get(getApplicationContext(), getValidator());
+ return this.context.getBean("mvcValidator", Validator.class);
}
@Override
@@ -437,7 +461,7 @@ public class WebMvcAutoConfiguration {
@Override
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
try {
- return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
+ return this.context.getBean(ConfigurableWebBindingInitializer.class);
}
catch (NoSuchBeanDefinitionException ex) {
return super.getConfigurableWebBindingInitializer();
@@ -486,6 +510,15 @@ 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
@@ -611,4 +644,128 @@ public class WebMvcAutoConfiguration {
}
+ /**
+ * Condition used to disable the default MVC validator registration. The
+ * {@link MvcValidatorPostProcessor} is used to configure 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 BeanFactoryPostProcessor} to deal with the MVC validator bean registration.
+ * Applies the following rules:
+ *
+ * - With no validators - Uses standard
+ * {@link WebMvcConfigurationSupport#mvcValidator()} logic.
+ * - With a single validator - Uses an alias.
+ * - With multiple validators - Registers a mvcValidator bean if not already
+ * defined.
+ *
+ */
+ @Order(Ordered.LOWEST_PRECEDENCE)
+ static class MvcValidatorPostProcessor
+ implements BeanDefinitionRegistryPostProcessor {
+
+ private static final String JSR303_VALIDATOR_CLASS = "javax.validation.Validator";
+
+ @Override
+ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
+ throws BeansException {
+ if (registry instanceof ListableBeanFactory) {
+ postProcess(registry, (ListableBeanFactory) registry);
+ }
+ }
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
+ throws BeansException {
+ }
+
+ private void postProcess(BeanDefinitionRegistry registry,
+ ListableBeanFactory beanFactory) {
+ String[] validatorBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
+ beanFactory, Validator.class, false, false);
+ if (validatorBeans.length == 0) {
+ registerMvcValidator(registry, beanFactory);
+ }
+ else if (validatorBeans.length == 1) {
+ registry.registerAlias(validatorBeans[0], "mvcValidator");
+ }
+ else {
+ if (!ObjectUtils.containsElement(validatorBeans, "mvcValidator")) {
+ registerMvcValidator(registry, beanFactory);
+ }
+ }
+ }
+
+ private void registerMvcValidator(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 ce9b93a3c6c..0f68897c967 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,6 +32,7 @@ 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;
@@ -322,9 +323,9 @@ public class SpringBootWebSecurityConfigurationTests {
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.class,
- DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
- HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
- PropertyPlaceholderAutoConfiguration.class })
+ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.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
new file mode 100644
index 00000000000..04cc4e8c075
--- /dev/null
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java
@@ -0,0 +1,116 @@
+/*
+ * 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/SpringValidatorTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/SpringValidatorTests.java
deleted file mode 100644
index cb9a5bcc434..00000000000
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/SpringValidatorTests.java
+++ /dev/null
@@ -1,151 +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 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.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-/**
- * Tests for {@link SpringValidator}.
- *
- * @author Stephane Nicoll
- */
-public class SpringValidatorTests {
-
- private AnnotationConfigApplicationContext context;
-
- @After
- public void close() {
- if (this.context != null) {
- this.context.close();
- }
- }
-
- @Test
- public void wrapLocalValidatorFactoryBean() {
- SpringValidator 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 SpringValidator load(Class> config) {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
- ctx.register(config);
- ctx.refresh();
- this.context = ctx;
- return this.context.getBean(SpringValidator.class);
- }
-
- @Configuration
- static class LocalValidatorFactoryBeanConfig {
-
- @Bean
- public LocalValidatorFactoryBean validator() {
- return new LocalValidatorFactoryBean();
- }
-
- @Bean
- public SpringValidator wrapper() {
- return new SpringValidator(validator(), true);
- }
-
- }
-
- @Configuration
- static class NonManagedBeanConfig {
-
- private final LocalValidatorFactoryBean validator = mock(
- LocalValidatorFactoryBean.class);
-
- @Bean
- public SpringValidator wrapper() {
- return new SpringValidator(this.validator, false);
- }
-
- }
-
- @Configuration
- static class ManagedBeanConfig {
-
- private final LocalValidatorFactoryBean validator = mock(
- LocalValidatorFactoryBean.class);
-
- @Bean
- public SpringValidator wrapper() {
- return new SpringValidator(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/validation/ValidationAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java
index 1ea207316ca..df2e7e3cd9e 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
@@ -32,9 +32,12 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
+import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
/**
* Tests for {@link ValidationAutoConfiguration}.
@@ -56,45 +59,94 @@ public class ValidationAutoConfigurationTests {
}
@Test
- public void validationIsEnabled() {
- load(SampleService.class);
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
- SampleService service = this.context.getBean(SampleService.class);
- service.doSomething("Valid");
- this.thrown.expect(ConstraintViolationException.class);
- service.doSomething("KO");
+ public void validationAutoConfigurationShouldConfigureJsrAndSpringValidator()
+ throws Exception {
+ 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");
}
@Test
- public void validationUsesCglibProxy() {
- load(DefaultAnotherSampleService.class);
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
- DefaultAnotherSampleService service = this.context
- .getBean(DefaultAnotherSampleService.class);
- service.doSomething(42);
- this.thrown.expect(ConstraintViolationException.class);
- service.doSomething(2);
+ public void validationAutoConfigurationWhenUserProvidesValidatorShouldBackOff()
+ throws Exception {
+ 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");
}
@Test
- public void validationCanBeConfiguredToUseJdkProxy() {
+ public void validationAutoConfigurationWhenUserProvidesJsrOnlyShouldAdaptIt()
+ throws Exception {
+ load(UserDefinedJsrValidatorConfig.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).isNotEqualTo(springValidator);
+ assertThat(springValidator).isInstanceOf(DelegatingValidator.class);
+ assertThat(jsrValidatorNames).containsExactly("customValidator");
+ assertThat(springValidatorNames).containsExactly("jsr303ValidatorAdapter");
+ }
+
+ @Test
+ public void validationAutoConfigurationShouldBeEnabled() {
+ load(ClassWithConstraint.class);
+ assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
+ ClassWithConstraint service = this.context.getBean(ClassWithConstraint.class);
+ service.call("Valid");
+ this.thrown.expect(ConstraintViolationException.class);
+ service.call("KO");
+ }
+
+ @Test
+ public void validationAutoConfigurationShouldUseCglibProxy() {
+ load(ImplementationOfInterfaceWithConstraint.class);
+ assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
+ ImplementationOfInterfaceWithConstraint service = this.context
+ .getBean(ImplementationOfInterfaceWithConstraint.class);
+ service.call(42);
+ this.thrown.expect(ConstraintViolationException.class);
+ service.call(2);
+ }
+
+ @Test
+ public void validationAutoConfigurationWhenProxyTargetClassIsFalseShouldUseJdkProxy() {
load(AnotherSampleServiceConfiguration.class,
"spring.aop.proxy-target-class=false");
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
- assertThat(this.context.getBeansOfType(DefaultAnotherSampleService.class))
- .isEmpty();
- AnotherSampleService service = this.context.getBean(AnotherSampleService.class);
- service.doSomething(42);
+ assertThat(this.context
+ .getBeansOfType(ImplementationOfInterfaceWithConstraint.class)).isEmpty();
+ InterfaceWithConstraint service = this.context
+ .getBean(InterfaceWithConstraint.class);
+ service.call(42);
this.thrown.expect(ConstraintViolationException.class);
- service.doSomething(2);
+ service.call(2);
}
@Test
- public void userDefinedMethodValidationPostProcessorTakesPrecedence() {
- load(SampleConfiguration.class);
+ public void validationAutoConfigurationWhenUserDefinesMethodValidationPostProcessorShouldBackOff() {
+ load(UserDefinedMethodValidationConfig.class);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
Object userMethodValidationPostProcessor = this.context
- .getBean("testMethodValidationPostProcessor");
+ .getBean("customMethodValidationPostProcessor");
assertThat(this.context.getBean(MethodValidationPostProcessor.class))
.isSameAs(userMethodValidationPostProcessor);
assertThat(this.context.getBeansOfType(MethodValidationPostProcessor.class))
@@ -115,47 +167,73 @@ public class ValidationAutoConfigurationTests {
this.context = ctx;
}
- @Validated
- static class SampleService {
+ @Configuration
+ static class Config {
- public void doSomething(@Size(min = 3, max = 10) String name) {
+ }
+ @Configuration
+ static class UserDefinedValidatorConfig {
+
+ @Bean
+ public OptionalValidatorFactoryBean customValidator() {
+ return new OptionalValidatorFactoryBean();
}
}
- interface AnotherSampleService {
+ @Configuration
+ static class UserDefinedJsrValidatorConfig {
+
+ @Bean
+ public Validator customValidator() {
+ return mock(Validator.class);
+ }
- void doSomething(@Min(42) Integer counter);
}
- @Validated
- static class DefaultAnotherSampleService implements AnotherSampleService {
-
- @Override
- public void doSomething(Integer counter) {
+ @Configuration
+ static class UserDefinedMethodValidationConfig {
+ @Bean
+ public MethodValidationPostProcessor customMethodValidationPostProcessor() {
+ return new MethodValidationPostProcessor();
}
+
}
@Configuration
static class AnotherSampleServiceConfiguration {
@Bean
- public AnotherSampleService anotherSampleService() {
- return new DefaultAnotherSampleService();
+ public InterfaceWithConstraint implementationOfInterfaceWithConstraint() {
+ return new ImplementationOfInterfaceWithConstraint();
}
}
- @Configuration
- static class SampleConfiguration {
+ @Validated
+ static class ClassWithConstraint {
+
+ public void call(@Size(min = 3, max = 10) String name) {
- @Bean
- public MethodValidationPostProcessor testMethodValidationPostProcessor() {
- return new MethodValidationPostProcessor();
}
}
+ interface InterfaceWithConstraint {
+
+ void call(@Min(42) Integer counter);
+ }
+
+ @Validated
+ static class ImplementationOfInterfaceWithConstraint
+ implements InterfaceWithConstraint {
+
+ @Override
+ public void call(Integer counter) {
+
+ }
+ }
+
}
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAnnotationAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAnnotationAutoConfigurationTests.java
index e3d6820ec62..91dbee202b4 100644
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAnnotationAutoConfigurationTests.java
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAnnotationAutoConfigurationTests.java
@@ -16,15 +16,19 @@
package org.springframework.boot.autoconfigure.web.reactive;
+import java.util.ArrayList;
+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.DirectFieldAccessor;
-import org.springframework.boot.autoconfigure.validation.SpringValidator;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.boot.autoconfigure.validation.DelegatingValidator;
+import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationTests.Config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
@@ -36,9 +40,9 @@ import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
-import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
@@ -65,6 +69,9 @@ import static org.mockito.Mockito.mock;
*/
public class WebFluxAnnotationAutoConfigurationTests {
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
private GenericReactiveWebApplicationContext context;
@Test
@@ -165,64 +172,134 @@ public class WebFluxAnnotationAutoConfigurationTests {
}
@Test
- public void validationNoJsr303ValidatorExposedByDefault() {
+ public void validatorWhenSuppliedByConfigurerShouldThrowException() throws Exception {
+ this.thrown.expect(BeanCreationException.class);
+ this.thrown.expectMessage("unexpected validator configuration");
+ load(ValidatorWebFluxConfigurer.class);
+ }
+
+ @Test
+ public void validatorWhenAutoConfiguredShouldUseAlias() throws Exception {
load();
- assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
- assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
- .isEmpty();
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
+ 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");
}
@Test
- public void validationCustomConfigurerTakesPrecedence() {
- load(WebFluxValidator.class);
- assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
- assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
- .isEmpty();
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
- Validator validator = this.context.getBean(Validator.class);
- assertThat(validator)
- .isSameAs(this.context.getBean(WebFluxValidator.class).validator);
+ 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 validationCustomConfigurerTakesPrecedenceAndDoNotExposeJsr303() {
- load(WebFluxJsr303Validator.class);
- assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
- assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
- .isEmpty();
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
- Validator validator = this.context.getBean(Validator.class);
- assertThat(validator).isInstanceOf(SpringValidator.class);
- assertThat(((SpringValidator) validator).getTarget())
- .isSameAs(this.context.getBean(WebFluxJsr303Validator.class).validator);
+ 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);
+ 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");
}
@Test
- public void validationJsr303CustomValidatorReusedAsSpringValidator() {
- load(CustomValidator.class);
- assertThat(this.context.getBeansOfType(ValidatorFactory.class)).hasSize(1);
- assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
- .hasSize(1);
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(2);
- Validator validator = this.context.getBean("webFluxValidator", Validator.class);
- assertThat(validator).isInstanceOf(SpringValidator.class);
- assertThat(((SpringValidator) validator).getTarget())
- .isSameAs(this.context.getBean(javax.validation.Validator.class));
+ 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);
+ 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");
}
@Test
- public void validationJsr303ValidatorExposedAsSpringValidator() {
- load(Jsr303Validator.class);
- assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
- assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
- .hasSize(1);
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
- Validator validator = this.context.getBean(Validator.class);
- assertThat(validator).isInstanceOf(SpringValidator.class);
- SpringValidatorAdapter target = ((SpringValidator) validator).getTarget();
- assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator"))
- .isSameAs(this.context.getBean(javax.validation.Validator.class));
+ 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);
+ 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(this.context.getBean(Validator.class))
+ .isEqualTo(customSpringValidator);
+ assertThat(springValidatorBeans).containsExactly("customSpringValidator");
+ assertThat(jsrValidatorBeans).containsExactly("customJsrValidator");
+ }
+
+ @Test
+ public void validatorWhenExcludingValidatorAutoConfigurationShouldUseMvc()
+ 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();
+ }
+
+ @Test
+ public void validatorWhenMultipleValidatorsAndNoWebFluxValidatorShouldAddMvc()
+ throws Exception {
+ load(MultipleValidatorsAndNoWebFluxValidator.class);
+ Object customValidator1 = this.context.getBean("customValidator1");
+ Object customValidator2 = this.context.getBean("customValidator2");
+ Object webFluxValidator = this.context.getBean("webFluxValidator");
+ 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();
+ }
+
+ @Test
+ public void validatorWhenMultipleValidatorsAndWebFluxValidatorShouldUseMvc()
+ throws Exception {
+ load(MultipleValidatorsAndWebFluxValidator.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).isNotSameAs(customValidator);
+ assertThat(springValidatorBeans).containsExactly("customValidator",
+ "webFluxValidator");
+ assertThat(jsrValidatorBeans).isEmpty();
}
private void load(String... environment) {
@@ -230,13 +307,24 @@ public class WebFluxAnnotationAutoConfigurationTests {
}
private void load(Class> config, String... environment) {
+ load(config, null, environment);
+ }
+
+ private void load(Class> config, Class>[] exclude, String... environment) {
this.context = new GenericReactiveWebApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, environment);
+ List> configClasses = new ArrayList<>();
if (config != null) {
- this.context.register(config);
+ configClasses.add(config);
}
- this.context.register(BaseConfiguration.class);
+ configClasses.addAll(Arrays.asList(Config.class,
+ ValidationAutoConfiguration.class, BaseConfiguration.class));
+ if (!ObjectUtils.isEmpty(exclude)) {
+ configClasses.removeAll(Arrays.asList(exclude));
+ }
+ this.context.register(configClasses.toArray(new Class>[configClasses.size()]));
this.context.refresh();
+
}
@Configuration
@@ -291,47 +379,88 @@ public class WebFluxAnnotationAutoConfigurationTests {
}
@Configuration
- protected static class WebFluxValidator implements WebFluxConfigurer {
-
- private final Validator validator = mock(Validator.class);
+ protected static class ValidatorWebFluxConfigurer implements WebFluxConfigurer {
@Override
public Optional getValidator() {
- return Optional.of(this.validator);
+ return Optional.of(mock(Validator.class));
}
}
@Configuration
- protected static class WebFluxJsr303Validator implements WebFluxConfigurer {
-
- private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
-
- @Override
- public Optional getValidator() {
- return Optional.of(this.validator);
- }
-
- }
-
- @Configuration
- static class Jsr303Validator {
+ static class UserDefinedSpringOnlyValidator {
@Bean
- public javax.validation.Validator jsr303Validator() {
+ public Validator customValidator() {
+ return mock(Validator.class);
+ }
+
+ }
+
+ @Configuration
+ static class UserDefinedJsr303Validator {
+
+ @Bean
+ public javax.validation.Validator customValidator() {
return mock(javax.validation.Validator.class);
}
}
@Configuration
- static class CustomValidator {
+ static class UserDefinedSingleJsr303AndSpringValidator {
@Bean
- public Validator customValidator() {
+ public LocalValidatorFactoryBean customValidator() {
return new LocalValidatorFactoryBean();
}
}
+ @Configuration
+ static class UserDefinedJsr303AndSpringValidator {
+
+ @Bean
+ public javax.validation.Validator customJsrValidator() {
+ 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 {
+
+ @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 2fdca55954a..f1d39693974 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,7 +27,6 @@ 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;
@@ -37,11 +36,13 @@ 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.SpringValidator;
+import org.springframework.boot.autoconfigure.validation.DelegatingValidator;
+import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
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;
@@ -62,11 +63,11 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
-import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.filter.HttpPutFormContentFilter;
@@ -658,76 +659,154 @@ public class WebMvcAutoConfigurationTests {
}
@Test
- public void validationNoJsr303ValidatorExposedByDefault() {
+ public void validatorWhenSuppliedByConfigurerShouldThrowException() throws Exception {
+ this.thrown.expect(BeanCreationException.class);
+ this.thrown.expectMessage("unexpected validator configuration");
+ load(ValidatorWebMvcConfigurer.class);
+ }
+
+ @Test
+ public void validatorWhenAutoConfiguredShouldUseAlias() throws Exception {
load();
- assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
- assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
- .isEmpty();
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
+ 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");
}
@Test
- public void validationCustomConfigurerTakesPrecedence() {
- load(MvcValidator.class);
- assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
- assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
- .isEmpty();
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
- Validator validator = this.context.getBean(Validator.class);
- assertThat(validator)
- .isSameAs(this.context.getBean(MvcValidator.class).validator);
+ 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);
+ 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 validationCustomConfigurerTakesPrecedenceAndDoNotExposeJsr303() {
- load(MvcJsr303Validator.class);
- assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
- assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
- .isEmpty();
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
- Validator validator = this.context.getBean(Validator.class);
- assertThat(validator).isInstanceOf(SpringValidator.class);
- assertThat(((SpringValidator) validator).getTarget())
- .isSameAs(this.context.getBean(MvcJsr303Validator.class).validator);
+ 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");
}
@Test
- public void validationJsr303CustomValidatorReusedAsSpringValidator() {
- load(CustomValidator.class);
- assertThat(this.context.getBeansOfType(ValidatorFactory.class)).hasSize(1);
- assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
- .hasSize(1);
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(2);
- Validator validator = this.context.getBean("mvcValidator", Validator.class);
- assertThat(validator).isInstanceOf(SpringValidator.class);
- assertThat(((SpringValidator) validator).getTarget())
- .isSameAs(this.context.getBean(javax.validation.Validator.class));
+ 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);
+ 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");
}
@Test
- public void validationJsr303ValidatorExposedAsSpringValidator() {
- load(Jsr303Validator.class);
- assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
- assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
- .hasSize(1);
- assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
- Validator validator = this.context.getBean(Validator.class);
- assertThat(validator).isInstanceOf(SpringValidator.class);
- SpringValidatorAdapter target = ((SpringValidator) validator).getTarget();
- assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator"))
- .isSameAs(this.context.getBean(javax.validation.Validator.class));
+ 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);
+ 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(this.context.getBean(Validator.class))
+ .isEqualTo(customSpringValidator);
+ assertThat(springValidatorBeans).containsExactly("customSpringValidator");
+ assertThat(jsrValidatorBeans).containsExactly("customJsrValidator");
+ }
+
+ @Test
+ public void validatorWhenExcludingValidatorAutoConfigurationShouldUseMvc()
+ throws Exception {
+ load(null, new Class[] { ValidationAutoConfiguration.class });
+ Object mvcValidator = this.context.getBean("mvcValidator");
+ 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();
+ }
+
+ @Test
+ public void validatorWhenMultipleValidatorsAndMvcValidatorShouldUseMvc()
+ throws Exception {
+ load(MultipleValidatorsAndMvcValidator.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(springValidatorBeans).containsExactly("customValidator",
+ "mvcValidator");
+ assertThat(jsrValidatorBeans).isEmpty();
}
private void load(Class> config, String... environment) {
+ load(config, null, environment);
+ }
+
+ private void load(Class> config, Class>[] exclude, String... environment) {
this.context = new AnnotationConfigServletWebServerApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, environment);
List> configClasses = new ArrayList<>();
if (config != null) {
configClasses.add(config);
}
- configClasses.addAll(Arrays.asList(Config.class, WebMvcAutoConfiguration.class,
+ configClasses.addAll(Arrays.asList(Config.class,
+ ValidationAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class));
+ if (!ObjectUtils.isEmpty(exclude)) {
+ configClasses.removeAll(Arrays.asList(exclude));
+ }
this.context.register(configClasses.toArray(new Class>[configClasses.size()]));
this.context.refresh();
}
@@ -897,47 +976,88 @@ public class WebMvcAutoConfigurationTests {
}
@Configuration
- protected static class MvcValidator extends WebMvcConfigurerAdapter {
-
- private final Validator validator = mock(Validator.class);
+ protected static class ValidatorWebMvcConfigurer extends WebMvcConfigurerAdapter {
@Override
public Validator getValidator() {
- return this.validator;
+ return mock(Validator.class);
}
}
@Configuration
- protected static class MvcJsr303Validator extends WebMvcConfigurerAdapter {
-
- private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
-
- @Override
- public Validator getValidator() {
- return this.validator;
- }
-
- }
-
- @Configuration
- static class Jsr303Validator {
+ static class UserDefinedSpringOnlyValidator {
@Bean
- public javax.validation.Validator jsr303Validator() {
+ public Validator customValidator() {
+ return mock(Validator.class);
+ }
+
+ }
+
+ @Configuration
+ static class UserDefinedJsr303Validator {
+
+ @Bean
+ public javax.validation.Validator customValidator() {
return mock(javax.validation.Validator.class);
}
}
@Configuration
- static class CustomValidator {
+ static class UserDefinedSingleJsr303AndSpringValidator {
@Bean
- public Validator customValidator() {
+ public LocalValidatorFactoryBean customValidator() {
return new LocalValidatorFactoryBean();
}
}
+ @Configuration
+ static class UserDefinedJsr303AndSpringValidator {
+
+ @Bean
+ public javax.validation.Validator customJsrValidator() {
+ 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);
+ }
+
+ @Bean
+ public Validator customValidator2() {
+ return mock(Validator.class);
+ }
+
+ }
+
+ @Configuration
+ static class MultipleValidatorsAndMvcValidator {
+
+ @Bean
+ public Validator customValidator() {
+ return mock(Validator.class);
+ }
+
+ @Bean
+ public Validator mvcValidator() {
+ 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 255d07e773e..ce092308ece 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,6 +36,7 @@ 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;
@@ -129,9 +130,9 @@ public class BasicErrorControllerDirectMockMvcTests {
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.class,
- DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
- HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
- PropertyPlaceholderAutoConfiguration.class })
+ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.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 881dc9cf32e..45e451a9a5e 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,6 +36,7 @@ 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;
@@ -131,9 +132,9 @@ public class BasicErrorControllerMockMvcTests {
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryAutoConfiguration.class,
- DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
- HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
- PropertyPlaceholderAutoConfiguration.class })
+ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
+ WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
+ ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
private @interface MinimalWebConfiguration {
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionSetGroovyCompilerConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionSetGroovyCompilerConfiguration.java
index b3e0ee79194..d4a6b2ec19c 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionSetGroovyCompilerConfiguration.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionSetGroovyCompilerConfiguration.java
@@ -92,4 +92,9 @@ public class OptionSetGroovyCompilerConfiguration implements GroovyCompilerConfi
return this.repositoryConfiguration;
}
+ @Override
+ public boolean isQuiet() {
+ return false;
+ }
+
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java
index 97b32f30cc9..ed480ed6f60 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java
@@ -140,7 +140,7 @@ public class RunCommand extends OptionParsingCommand {
@Override
public Level getLogLevel() {
- if (getOptions().has(RunOptionHandler.this.quietOption)) {
+ if (isQuiet()) {
return Level.OFF;
}
if (getOptions().has(RunOptionHandler.this.verboseOption)) {
@@ -149,6 +149,11 @@ public class RunCommand extends OptionParsingCommand {
return Level.INFO;
}
+ @Override
+ public boolean isQuiet() {
+ return getOptions().has(RunOptionHandler.this.quietOption);
+ }
+
}
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java
index 0e31ed167fc..9f9172455d1 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java
@@ -95,7 +95,8 @@ public class GroovyCompiler {
new SpringBootDependenciesDependencyManagement());
AetherGrapeEngine grapeEngine = AetherGrapeEngineFactory.create(this.loader,
- configuration.getRepositoryConfiguration(), resolutionContext);
+ configuration.getRepositoryConfiguration(), resolutionContext,
+ configuration.isQuiet());
GrapeEngineInstaller.install(grapeEngine);
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java
index 7741f3242cf..7fd8462dc86 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java
@@ -71,4 +71,10 @@ public interface GroovyCompilerConfiguration {
*/
List getRepositoryConfiguration();
+ /**
+ * Returns if running in quiet mode.
+ * @return {@code true} if running in quiet mode
+ */
+ boolean isQuiet();
+
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java
index ec4be5b5b09..976473a4507 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java
@@ -77,7 +77,7 @@ public class AetherGrapeEngine implements GrapeEngine {
RepositorySystem repositorySystem,
DefaultRepositorySystemSession repositorySystemSession,
List remoteRepositories,
- DependencyResolutionContext resolutionContext) {
+ DependencyResolutionContext resolutionContext, boolean quiet) {
this.classLoader = classLoader;
this.repositorySystem = repositorySystem;
this.session = repositorySystemSession;
@@ -88,12 +88,14 @@ public class AetherGrapeEngine implements GrapeEngine {
for (RemoteRepository repository : remotes) {
addRepository(repository);
}
- this.progressReporter = getProgressReporter(this.session);
+ this.progressReporter = getProgressReporter(this.session, quiet);
}
- private ProgressReporter getProgressReporter(DefaultRepositorySystemSession session) {
- String progressReporter = System.getProperty(
- "org.springframework.boot.cli.compiler.grape.ProgressReporter");
+ private ProgressReporter getProgressReporter(DefaultRepositorySystemSession session,
+ boolean quiet) {
+ String progressReporter = (quiet ? "none"
+ : System.getProperty(
+ "org.springframework.boot.cli.compiler.grape.ProgressReporter"));
if ("detail".equals(progressReporter)
|| Boolean.getBoolean("groovy.grape.report.downloads")) {
return new DetailedProgressReporter(session, System.out);
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java
index 8878a67041e..946e5cd1d29 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java
@@ -44,27 +44,21 @@ public abstract class AetherGrapeEngineFactory {
public static AetherGrapeEngine create(GroovyClassLoader classLoader,
List repositoryConfigurations,
- DependencyResolutionContext dependencyResolutionContext) {
-
+ DependencyResolutionContext dependencyResolutionContext, boolean quiet) {
RepositorySystem repositorySystem = createServiceLocator()
.getService(RepositorySystem.class);
-
DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils
.newSession();
-
ServiceLoader autoConfigurations = ServiceLoader
.load(RepositorySystemSessionAutoConfiguration.class);
-
for (RepositorySystemSessionAutoConfiguration autoConfiguration : autoConfigurations) {
autoConfiguration.apply(repositorySystemSession, repositorySystem);
}
-
new DefaultRepositorySystemSessionAutoConfiguration()
.apply(repositorySystemSession, repositorySystem);
-
return new AetherGrapeEngine(classLoader, repositorySystem,
repositorySystemSession, createRepositories(repositoryConfigurations),
- dependencyResolutionContext);
+ dependencyResolutionContext, quiet);
}
private static ServiceLocator createServiceLocator() {
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolverTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolverTests.java
index 613612c0c9a..79760c550ef 100644
--- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolverTests.java
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolverTests.java
@@ -81,6 +81,11 @@ public class GroovyGrabDependencyResolverTests {
return new String[] { "." };
}
+ @Override
+ public boolean isQuiet() {
+ return false;
+ }
+
};
this.resolver = new GroovyGrabDependencyResolver(configuration);
}
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java
index 048b0d04aed..57764484755 100644
--- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java
@@ -59,7 +59,7 @@ public class AetherGrapeEngineTests {
dependencyResolutionContext.addDependencyManagement(
new SpringBootDependenciesDependencyManagement());
return AetherGrapeEngineFactory.create(this.groovyClassLoader,
- repositoryConfigurations, dependencyResolutionContext);
+ repositoryConfigurations, dependencyResolutionContext, false);
}
@Test
diff --git a/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc b/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc
index 45fc7ba4184..7d8ac458eb1 100644
--- a/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc
+++ b/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc
@@ -283,6 +283,8 @@ the `Main-Class` attribute and leave out `Start-Class`.
* `loader.path` can contain directories (scanned recursively for jar and zip files),
archive paths, a directory within an archive that is scanned for jar files (for
example, `dependencies.jar!/lib`), or wildcard patterns (for the default JVM behavior).
+ Archive paths can be relative to `loader.home`, or anywhere in the file system with a
+ `jar:file:` prefix.
* `loader.path` (if empty) defaults to `BOOT-INF/lib` (meaning a local directory or a
nested one if running from an archive). Because of this `PropertiesLauncher` behaves the
same as `JarLauncher` when no additional configuration is provided.
diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java
index edea2fdbd88..96d70f16b7c 100644
--- a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java
+++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java
@@ -250,8 +250,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr
protected Class>[] getOrFindConfigurationClasses(
MergedContextConfiguration mergedConfig) {
Class>[] classes = mergedConfig.getClasses();
- if (containsNonTestComponent(classes) || mergedConfig.hasLocations()
- || !mergedConfig.getContextInitializerClasses().isEmpty()) {
+ if (containsNonTestComponent(classes) || mergedConfig.hasLocations()) {
return classes;
}
Class> found = new SpringBootConfigurationFinder()
diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperWithInitializersTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperWithInitializersTests.java
new file mode 100644
index 00000000000..c572dbbf199
--- /dev/null
+++ b/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperWithInitializersTests.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012-2016 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.test.context.bootstrap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTestContextBootstrapper;
+import org.springframework.boot.test.context.bootstrap.SpringBootTestContextBootstrapperWithInitializersTests.CustomInitializer;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.context.BootstrapWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for {@link SpringBootTestContextBootstrapper} with and
+ * {@link ApplicationContextInitializer}.
+ *
+ * @author Phillip Webb
+ */
+@RunWith(SpringRunner.class)
+@BootstrapWith(SpringBootTestContextBootstrapper.class)
+@ContextConfiguration(initializers = CustomInitializer.class)
+public class SpringBootTestContextBootstrapperWithInitializersTests {
+
+ @Autowired
+ private ApplicationContext context;
+
+ @Test
+ public void foundConfiguration() throws Exception {
+ Object bean = this.context
+ .getBean(SpringBootTestContextBootstrapperExampleConfig.class);
+ assertThat(bean).isNotNull();
+ }
+
+ // gh-8483
+
+ public static class CustomInitializer
+ implements ApplicationContextInitializer {
+
+ @Override
+ public void initialize(ConfigurableApplicationContext applicationContext) {
+ }
+
+ }
+
+}
diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java
index 677c2212d86..ee828072a07 100755
--- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java
+++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java
@@ -25,8 +25,10 @@ import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
+import java.util.Set;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -299,11 +301,9 @@ public class PropertiesLauncher extends Launcher {
List paths = new ArrayList<>();
for (String path : commaSeparatedPaths.split(",")) {
path = cleanupPath(path);
- // Empty path (i.e. the archive itself if running from a JAR) is always added
- // to the classpath so no need for it to be explicitly listed
- if (!path.equals("")) {
- paths.add(path);
- }
+ // "" means the user wants root of archive but not current directory
+ path = ("".equals(path) ? "/" : path);
+ paths.add(path);
}
if (paths.isEmpty()) {
paths.add("lib");
@@ -336,7 +336,13 @@ public class PropertiesLauncher extends Launcher {
@Override
protected ClassLoader createClassLoader(List archives) throws Exception {
- ClassLoader loader = super.createClassLoader(archives);
+ Set urls = new LinkedHashSet(archives.size());
+ for (Archive archive : archives) {
+ urls.add(archive.getUrl());
+ }
+ ClassLoader loader = new LaunchedURLClassLoader(urls.toArray(new URL[0]),
+ getClass().getClassLoader());
+ debug("Classpath: " + urls);
String customLoaderClassName = getProperty("loader.classLoader");
if (customLoaderClassName != null) {
loader = wrapWithCustomClassLoader(loader, customLoaderClassName);
@@ -454,13 +460,15 @@ public class PropertiesLauncher extends Launcher {
String root = cleanupPath(stripFileUrlPrefix(path));
List lib = new ArrayList<>();
File file = new File(root);
- if (!isAbsolutePath(root)) {
- file = new File(this.home, root);
- }
- if (file.isDirectory()) {
- debug("Adding classpath entries from " + file);
- Archive archive = new ExplodedArchive(file, false);
- lib.add(archive);
+ if (!"/".equals(root)) {
+ if (!isAbsolutePath(root)) {
+ file = new File(this.home, root);
+ }
+ if (file.isDirectory()) {
+ debug("Adding classpath entries from " + file);
+ Archive archive = new ExplodedArchive(file, false);
+ lib.add(archive);
+ }
}
Archive archive = getArchive(file);
if (archive != null) {
@@ -488,24 +496,46 @@ public class PropertiesLauncher extends Launcher {
return null;
}
- private List getNestedArchives(String root) throws Exception {
- if (root.startsWith("/")
- || this.parent.getUrl().equals(this.home.toURI().toURL())) {
+ private List getNestedArchives(String path) throws Exception {
+ Archive parent = this.parent;
+ String root = path;
+ if (!root.equals("/") && root.startsWith("/")
+ || parent.getUrl().equals(this.home.toURI().toURL())) {
// If home dir is same as parent archive, no need to add it twice.
return null;
}
- Archive parent = this.parent;
- if (root.startsWith("jar:file:") && root.contains("!")) {
+ if (root.contains("!")) {
int index = root.indexOf("!");
- String file = root.substring("jar:file:".length(), index);
- parent = new JarFileArchive(new File(file));
+ File file = new File(this.home, root.substring(0, index));
+ if (root.startsWith("jar:file:")) {
+ file = new File(root.substring("jar:file:".length(), index));
+ }
+ parent = new JarFileArchive(file);
root = root.substring(index + 1, root.length());
while (root.startsWith("/")) {
root = root.substring(1);
}
}
+ if (root.endsWith(".jar")) {
+ File file = new File(this.home, root);
+ if (file.exists()) {
+ parent = new JarFileArchive(file);
+ root = "";
+ }
+ }
+ if (root.equals("/") || root.equals("./") || root.equals(".")) {
+ // The prefix for nested jars is actually empty if it's at the root
+ root = "";
+ }
EntryFilter filter = new PrefixMatchingArchiveFilter(root);
- return parent.getNestedArchives(filter);
+ List archives = new ArrayList(parent.getNestedArchives(filter));
+ if (("".equals(root) || ".".equals(root)) && !path.endsWith(".jar")
+ && parent != this.parent) {
+ // You can't find the root with an entry filter so it has to be added
+ // explicitly. But don't add the root of the parent archive.
+ archives.add(parent);
+ }
+ return archives;
}
private void addNestedEntries(List lib) {
@@ -518,7 +548,7 @@ public class PropertiesLauncher extends Launcher {
@Override
public boolean matches(Entry entry) {
if (entry.isDirectory()) {
- return entry.getName().startsWith(JarLauncher.BOOT_INF_CLASSES);
+ return entry.getName().equals(JarLauncher.BOOT_INF_CLASSES);
}
return entry.getName().startsWith(JarLauncher.BOOT_INF_LIB);
}
@@ -607,6 +637,9 @@ public class PropertiesLauncher extends Launcher {
@Override
public boolean matches(Entry entry) {
+ if (entry.isDirectory()) {
+ return entry.getName().equals(this.prefix);
+ }
return entry.getName().startsWith(this.prefix) && this.filter.matches(entry);
}
diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java
index 3610844a85a..98be63ec579 100644
--- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java
+++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java
@@ -21,12 +21,13 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
+import org.assertj.core.api.Condition;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -36,6 +37,9 @@ import org.junit.rules.TemporaryFolder;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.loader.archive.Archive;
+import org.springframework.boot.loader.archive.ExplodedArchive;
+import org.springframework.boot.loader.archive.JarFileArchive;
+import org.springframework.core.io.FileSystemResource;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -72,6 +76,7 @@ public class PropertiesLauncherTests {
System.clearProperty("loader.config.name");
System.clearProperty("loader.config.location");
System.clearProperty("loader.system");
+ System.clearProperty("loader.classLoader");
}
@Test
@@ -131,6 +136,16 @@ public class PropertiesLauncherTests {
.isEqualTo("[.]");
}
+ @Test
+ public void testUserSpecifiedSlashPath() throws Exception {
+ System.setProperty("loader.path", "jars/");
+ PropertiesLauncher launcher = new PropertiesLauncher();
+ assertThat(ReflectionTestUtils.getField(launcher, "paths").toString())
+ .isEqualTo("[jars/]");
+ List archives = launcher.getClassPathArchives();
+ assertThat(archives).areExactly(1, endingWith("app.jar!/"));
+ }
+
@Test
public void testUserSpecifiedWildcardPath() throws Exception {
System.setProperty("loader.path", "jars/*");
@@ -153,13 +168,44 @@ public class PropertiesLauncherTests {
waitFor("Hello World");
}
+ @Test
+ public void testUserSpecifiedRootOfJarPath() throws Exception {
+ System.setProperty("loader.path",
+ "jar:file:./src/test/resources/nested-jars/app.jar!/");
+ PropertiesLauncher launcher = new PropertiesLauncher();
+ assertThat(ReflectionTestUtils.getField(launcher, "paths").toString())
+ .isEqualTo("[jar:file:./src/test/resources/nested-jars/app.jar!/]");
+ List archives = launcher.getClassPathArchives();
+ assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
+ assertThat(archives).areExactly(1, endingWith("app.jar!/"));
+ }
+
+ @Test
+ public void testUserSpecifiedRootOfJarPathWithDot() throws Exception {
+ System.setProperty("loader.path", "nested-jars/app.jar!/./");
+ PropertiesLauncher launcher = new PropertiesLauncher();
+ List archives = launcher.getClassPathArchives();
+ assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
+ assertThat(archives).areExactly(1, endingWith("app.jar!/"));
+ }
+
+ @Test
+ public void testUserSpecifiedRootOfJarPathWithDotAndJarPrefix() throws Exception {
+ System.setProperty("loader.path",
+ "jar:file:./src/test/resources/nested-jars/app.jar!/./");
+ PropertiesLauncher launcher = new PropertiesLauncher();
+ List archives = launcher.getClassPathArchives();
+ assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
+ }
+
@Test
public void testUserSpecifiedJarFileWithNestedArchives() throws Exception {
System.setProperty("loader.path", "nested-jars/app.jar");
System.setProperty("loader.main", "demo.Application");
PropertiesLauncher launcher = new PropertiesLauncher();
- launcher.launch(new String[0]);
- waitFor("Hello World");
+ List archives = launcher.getClassPathArchives();
+ assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
+ assertThat(archives).areExactly(1, endingWith("app.jar!/"));
}
@Test
@@ -209,11 +255,28 @@ public class PropertiesLauncherTests {
public void testCustomClassLoaderCreation() throws Exception {
System.setProperty("loader.classLoader", TestLoader.class.getName());
PropertiesLauncher launcher = new PropertiesLauncher();
- ClassLoader loader = launcher.createClassLoader(Collections.emptyList());
+ ClassLoader loader = launcher.createClassLoader(archives());
assertThat(loader).isNotNull();
assertThat(loader.getClass().getName()).isEqualTo(TestLoader.class.getName());
}
+ private List archives() throws Exception {
+ List archives = new ArrayList();
+ String path = System.getProperty("java.class.path");
+ for (String url : path.split(File.pathSeparator)) {
+ archives.add(archive(url));
+ }
+ return archives;
+ }
+
+ private Archive archive(String url) throws IOException {
+ File file = new FileSystemResource(url).getFile();
+ if (url.endsWith(".jar")) {
+ return new JarFileArchive(file);
+ }
+ return new ExplodedArchive(file);
+ }
+
@Test
public void testUserSpecifiedConfigPathWins() throws Exception {
@@ -280,6 +343,17 @@ public class PropertiesLauncherTests {
assertThat(timeout).as("Timed out waiting for (" + value + ")").isTrue();
}
+ private Condition endingWith(final String value) {
+ return new Condition() {
+
+ @Override
+ public boolean matches(Archive archive) {
+ return archive.toString().endsWith(value);
+ }
+
+ };
+ }
+
public static class TestLoader extends URLClassLoader {
public TestLoader(ClassLoader parent) {
diff --git a/spring-boot-tools/spring-boot-loader/src/test/resources/nested-jars/app.jar b/spring-boot-tools/spring-boot-loader/src/test/resources/nested-jars/app.jar
index a4365eec85b..5600ed279ef 100644
Binary files a/spring-boot-tools/spring-boot-loader/src/test/resources/nested-jars/app.jar and b/spring-boot-tools/spring-boot-loader/src/test/resources/nested-jars/app.jar differ