From 0435f122d4e16aafbd142d3525abb1c7aee79c1b Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 27 Feb 2017 19:06:36 +0100 Subject: [PATCH] Rework JSR-303 validation exposure with Spring MVC This commit improves the initial solution by actually overriding the `mvcValidator` `@Bean`. This gives us more control as whether a custom validator has been specified or not. We now wrap it regardless of it being custom or provided by auto-configuration. Closes gh-8223 --- .../web/WebMvcAutoConfiguration.java | 103 +++++++++--------- .../web/WebMvcAutoConfigurationTests.java | 25 +++++ 2 files changed, 77 insertions(+), 51 deletions(-) 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 4feacc1ec7..b8e14ad764 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 @@ -69,7 +69,6 @@ import org.springframework.util.StringUtils; import org.springframework.validation.DefaultMessageCodesResolver; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean; import org.springframework.validation.beanvalidation.SpringValidatorAdapter; import org.springframework.web.accept.ContentNegotiationManager; @@ -91,7 +90,6 @@ import org.springframework.web.servlet.config.annotation.ResourceChainRegistrati import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; @@ -160,8 +158,6 @@ public class WebMvcAutoConfiguration { private static final Log logger = LogFactory .getLog(WebMvcConfigurerAdapter.class); - private final ApplicationContext applicationContext; - private final ResourceProperties resourceProperties; private final WebMvcProperties mvcProperties; @@ -170,39 +166,20 @@ public class WebMvcAutoConfiguration { private final HttpMessageConverters messageConverters; - private final Validator userDefinedValidator; - final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; - public WebMvcAutoConfigurationAdapter(ApplicationContext applicationContext, - ResourceProperties resourceProperties, WebMvcProperties mvcProperties, - ListableBeanFactory beanFactory, HttpMessageConverters messageConverters, - ObjectProvider> webMvcConfigurers, + public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, + WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, + HttpMessageConverters messageConverters, ObjectProvider resourceHandlerRegistrationCustomizerProvider) { - this.applicationContext = applicationContext; this.resourceProperties = resourceProperties; this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConverters = messageConverters; - this.userDefinedValidator = findUserDefinedValidator( - webMvcConfigurers.getIfAvailable()); this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider .getIfAvailable(); } - private static Validator findUserDefinedValidator( - List webMvcConfigurers) { - if (webMvcConfigurers != null) { - for (WebMvcConfigurer webMvcConfigurer : webMvcConfigurers) { - Validator validator = webMvcConfigurer.getValidator(); - if (validator != null) { - return validator; - } - } - } - return null; - } - @Override public void configureMessageConverters(List> converters) { converters.addAll(this.messageConverters.getConverters()); @@ -298,23 +275,6 @@ public class WebMvcAutoConfiguration { } } - @Override - public Validator getValidator() { - // We want to make sure that the exposed 'mvcValidator' bean isn't going to - // expose the standard JSR-303 type - if (isJsr303Present() && this.userDefinedValidator == null) { - return new Jsr303ValidatorHandler(this.applicationContext) - .wrapJsr303Validator(); - - } - return null; // Keep default or user defined, if any - } - - private boolean isJsr303Present() { - return ClassUtils.isPresent(JSR303_VALIDATOR_CLASS, - this.applicationContext.getClassLoader()); - } - private Collection getBeansOfType(Class type) { return this.beanFactory.getBeansOfType(type).values(); } @@ -442,6 +402,22 @@ public class WebMvcAutoConfiguration { return super.requestMappingHandlerMapping(); } + @Bean + @Override + public Validator mvcValidator() { + if (isJsr303Present()) { + Validator userDefinedValidator = getValidator(); + return new Jsr303ValidatorHandler(getApplicationContext(), + userDefinedValidator).wrapJsr303Validator(); + } + return super.mvcValidator(); + } + + private boolean isJsr303Present() { + return ClassUtils.isPresent(JSR303_VALIDATOR_CLASS, + getApplicationContext().getClassLoader()); + } + @Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { if (this.mvcRegistrations != null @@ -587,21 +563,27 @@ public class WebMvcAutoConfiguration { private final ApplicationContext applicationContext; - Jsr303ValidatorHandler(ApplicationContext applicationContext) { + private final Validator userDefinedValidator; + + Jsr303ValidatorHandler(ApplicationContext applicationContext, + Validator userDefinedValidator) { this.applicationContext = applicationContext; + this.userDefinedValidator = userDefinedValidator; } public Validator wrapJsr303Validator() { try { - javax.validation.Validator validator = this.applicationContext - .getBean(javax.validation.Validator.class); - if (validator instanceof LocalValidatorFactoryBean) { - return new SpringValidatorAdapterWrapper( - (LocalValidatorFactoryBean) validator, true); + if (this.userDefinedValidator != null) { + if (this.userDefinedValidator instanceof javax.validation.Validator) { + return wrap((javax.validation.Validator) this.userDefinedValidator, false); + } + else { + return this.userDefinedValidator; + } } else { - return new SpringValidatorAdapterWrapper( - new SpringValidatorAdapter(validator), false); + return wrap(this.applicationContext.getBean( + javax.validation.Validator.class), true); } } catch (NoSuchBeanDefinitionException ex) { @@ -612,6 +594,25 @@ public class WebMvcAutoConfiguration { } } + + /** + * Wrap the specified {@code validator}. + * @param validator the validator to wrap + * @param bean {@code true} if the specified {@code validator} is a bean managed + * in the context + * @return a {@link Validator} wrapping the specified argument + */ + private Validator wrap(javax.validation.Validator validator, boolean bean) { + if (validator instanceof SpringValidatorAdapter) { + return new SpringValidatorAdapterWrapper( + (SpringValidatorAdapter) validator, bean); + } + else { + return new SpringValidatorAdapterWrapper( + new SpringValidatorAdapter(validator), false); + } + } + } } 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 3d6b27aa10..652ade4f8e 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 @@ -666,6 +666,19 @@ public class WebMvcAutoConfigurationTests { .validator); } + @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(SpringValidatorAdapterWrapper.class); + assertThat(((SpringValidatorAdapterWrapper) validator).getTarget()) + .isSameAs(this.context.getBean(MvcJsr303Validator.class).validator); + } + @Test public void validationJsr303CustomValidatorReusedAsSpringValidator() { load(CustomValidator.class); @@ -884,6 +897,18 @@ public class WebMvcAutoConfigurationTests { } + @Configuration + protected static class MvcJsr303Validator extends WebMvcConfigurerAdapter { + + private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + + @Override + public Validator getValidator() { + return this.validator; + } + + } + @Configuration static class Jsr303Validator {