diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcValidator.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/SpringValidator.java similarity index 92% rename from spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcValidator.java rename to spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/SpringValidator.java index 499e08dbfdd..cce7bec9f71 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcValidator.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/SpringValidator.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.web; +package org.springframework.boot.autoconfigure.validation; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; @@ -38,19 +38,19 @@ import org.springframework.validation.beanvalidation.SpringValidatorAdapter; * @author Stephane Nicoll * @author Phillip Webb */ -class WebMvcValidator implements SmartValidator, ApplicationContextAware, +public class SpringValidator implements SmartValidator, ApplicationContextAware, InitializingBean, DisposableBean { private final SpringValidatorAdapter target; private final boolean existingBean; - WebMvcValidator(SpringValidatorAdapter target, boolean existingBean) { + public SpringValidator(SpringValidatorAdapter target, boolean existingBean) { this.target = target; this.existingBean = existingBean; } - SpringValidatorAdapter getTarget() { + public final SpringValidatorAdapter getTarget() { return this.target; } @@ -131,10 +131,10 @@ class WebMvcValidator implements SmartValidator, ApplicationContextAware, private static Validator wrap(Validator validator, boolean existingBean) { if (validator instanceof javax.validation.Validator) { if (validator instanceof SpringValidatorAdapter) { - return new WebMvcValidator((SpringValidatorAdapter) validator, + return new SpringValidator((SpringValidatorAdapter) validator, existingBean); } - return new WebMvcValidator( + return new SpringValidator( new SpringValidatorAdapter((javax.validation.Validator) validator), existingBean); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java index 7777e0c2e9c..ecc9a6f6a66 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java @@ -43,6 +43,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean 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.validation.SpringValidator; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -404,7 +405,7 @@ public class WebMvcAutoConfiguration { getClass().getClassLoader())) { return super.mvcValidator(); } - return WebMvcValidator.get(getApplicationContext(), + return SpringValidator.get(getApplicationContext(), getValidator()); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfiguration.java index 5b675200b63..74053f9e362 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfiguration.java @@ -32,6 +32,7 @@ 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.web.ConditionalOnEnabledResourceChain; import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -45,6 +46,8 @@ import org.springframework.core.convert.converter.GenericConverter; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.http.CacheControl; +import org.springframework.util.ClassUtils; +import org.springframework.validation.Validator; import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.ResourceChainRegistration; @@ -71,15 +74,14 @@ import org.springframework.web.reactive.result.view.ViewResolver; @Configuration @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @ConditionalOnClass(WebFluxConfigurer.class) -@ConditionalOnMissingBean(RouterFunction.class) +@ConditionalOnMissingBean({ WebFluxConfigurationSupport.class, RouterFunction.class }) @AutoConfigureAfter(ReactiveWebServerAutoConfiguration.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) public class WebFluxAnnotationAutoConfiguration { @Configuration - @ConditionalOnMissingBean(WebFluxConfigurationSupport.class) @EnableConfigurationProperties({ResourceProperties.class, WebFluxProperties.class}) - @Import(DelegatingWebFluxConfiguration.class) + @Import(EnableWebFluxConfiguration.class) public static class WebFluxConfig implements WebFluxConfigurer { private static final Log logger = LogFactory.getLog(WebFluxConfig.class); @@ -178,6 +180,26 @@ public class WebFluxAnnotationAutoConfiguration { } } + /** + * Configuration equivalent to {@code @EnableWebFlux}. + */ + @Configuration + public static class EnableWebFluxConfiguration + extends DelegatingWebFluxConfiguration { + + @Override + @Bean + public Validator webFluxValidator() { + if (!ClassUtils.isPresent("javax.validation.Validator", + getClass().getClassLoader())) { + return super.webFluxValidator(); + } + return SpringValidator.get(getApplicationContext(), + getValidator()); + } + + } + @Configuration @ConditionalOnEnabledResourceChain static class ResourceChainCustomizerConfiguration { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcValidatorTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/SpringValidatorTests.java similarity index 87% rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcValidatorTests.java rename to spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/SpringValidatorTests.java index a0df0cf2393..183a07e50b0 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcValidatorTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/SpringValidatorTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.web; +package org.springframework.boot.autoconfigure.validation; import java.util.HashMap; @@ -37,11 +37,11 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** - * Tests for {@link WebMvcValidator}. + * Tests for {@link SpringValidator}. * * @author Stephane Nicoll */ -public class WebMvcValidatorTests { +public class SpringValidatorTests { private AnnotationConfigApplicationContext context; @@ -54,7 +54,7 @@ public class WebMvcValidatorTests { @Test public void wrapLocalValidatorFactoryBean() { - WebMvcValidator wrapper = load( + SpringValidator wrapper = load( LocalValidatorFactoryBeanConfig.class); assertThat(wrapper.supports(SampleData.class)).isTrue(); MapBindingResult errors = new MapBindingResult(new HashMap(), @@ -89,12 +89,12 @@ public class WebMvcValidatorTests { verify(validator, times(0)).destroy(); } - private WebMvcValidator load(Class config) { + private SpringValidator load(Class config) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(config); ctx.refresh(); this.context = ctx; - return this.context.getBean(WebMvcValidator.class); + return this.context.getBean(SpringValidator.class); } @Configuration @@ -106,8 +106,8 @@ public class WebMvcValidatorTests { } @Bean - public WebMvcValidator wrapper() { - return new WebMvcValidator(validator(), true); + public SpringValidator wrapper() { + return new SpringValidator(validator(), true); } } @@ -119,8 +119,8 @@ public class WebMvcValidatorTests { LocalValidatorFactoryBean.class); @Bean - public WebMvcValidator wrapper() { - return new WebMvcValidator(this.validator, false); + public SpringValidator wrapper() { + return new SpringValidator(this.validator, false); } } @@ -132,8 +132,8 @@ public class WebMvcValidatorTests { LocalValidatorFactoryBean.class); @Bean - public WebMvcValidator wrapper() { - return new WebMvcValidator(this.validator, true); + public SpringValidator wrapper() { + return new SpringValidator(this.validator, true); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java index bd6fcbab984..c157724584a 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java @@ -39,6 +39,7 @@ import org.junit.rules.ExpectedException; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.validation.SpringValidator; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WelcomePageHandlerMapping; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; @@ -674,8 +675,8 @@ public class WebMvcAutoConfigurationTests { .isEmpty(); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); Validator validator = this.context.getBean(Validator.class); - assertThat(validator).isInstanceOf(WebMvcValidator.class); - assertThat(((WebMvcValidator) validator).getTarget()) + assertThat(validator).isInstanceOf(SpringValidator.class); + assertThat(((SpringValidator) validator).getTarget()) .isSameAs(this.context.getBean(MvcJsr303Validator.class).validator); } @@ -687,8 +688,8 @@ public class WebMvcAutoConfigurationTests { .hasSize(1); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(2); Validator validator = this.context.getBean("mvcValidator", Validator.class); - assertThat(validator).isInstanceOf(WebMvcValidator.class); - assertThat(((WebMvcValidator) validator).getTarget()) + assertThat(validator).isInstanceOf(SpringValidator.class); + assertThat(((SpringValidator) validator).getTarget()) .isSameAs(this.context.getBean(javax.validation.Validator.class)); } @@ -700,8 +701,8 @@ public class WebMvcAutoConfigurationTests { .hasSize(1); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); Validator validator = this.context.getBean(Validator.class); - assertThat(validator).isInstanceOf(WebMvcValidator.class); - SpringValidatorAdapter target = ((WebMvcValidator) validator) + assertThat(validator).isInstanceOf(SpringValidator.class); + SpringValidatorAdapter target = ((SpringValidator) validator) .getTarget(); assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator")) .isSameAs(this.context.getBean(javax.validation.Validator.class)); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfigurationTests.java index 60ca28f257d..d61b8a52cf3 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webflux/WebFluxAnnotationAutoConfigurationTests.java @@ -16,8 +16,14 @@ package org.springframework.boot.autoconfigure.webflux; +import java.util.Optional; + +import javax.validation.ValidatorFactory; + import org.junit.Test; +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.boot.autoconfigure.validation.SpringValidator; import org.springframework.boot.context.GenericReactiveWebApplicationContext; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.util.EnvironmentTestUtils; @@ -28,9 +34,13 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.io.ClassPathResource; import org.springframework.http.server.reactive.HttpHandler; +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; +import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; import org.springframework.web.reactive.resource.CachingResourceResolver; import org.springframework.web.reactive.resource.CachingResourceTransformer; @@ -41,7 +51,6 @@ import org.springframework.web.reactive.result.method.annotation.RequestMappingH import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; import org.springframework.web.reactive.result.view.ViewResolver; -import org.springframework.web.server.adapter.HttpWebHandlerAdapter; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -152,6 +161,68 @@ public class WebFluxAnnotationAutoConfigurationTests { ); } + @Test + public void validationNoJsr303ValidatorExposedByDefault() { + 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); + } + + @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); + } + + @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); + } + + @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)); + } + + @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)); + } + private void load(String... environment) { load(null, environment); } @@ -216,4 +287,49 @@ public class WebFluxAnnotationAutoConfigurationTests { return (serverHttpRequest, serverHttpResponse) -> null; } } + + @Configuration + protected static class WebFluxValidator implements WebFluxConfigurer { + + private final Validator validator = mock(Validator.class); + + @Override + public Optional getValidator() { + return Optional.of(this.validator); + } + + } + + @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 { + + @Bean + public javax.validation.Validator jsr303Validator() { + return mock(javax.validation.Validator.class); + } + + } + + @Configuration + static class CustomValidator { + + @Bean + public Validator customValidator() { + return new LocalValidatorFactoryBean(); + } + + } + }