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
pull/8428/head
Stephane Nicoll 8 years ago
parent d8f62c46ad
commit 0435f122d4

@ -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<List<WebMvcConfigurer>> webMvcConfigurers,
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
HttpMessageConverters messageConverters,
ObjectProvider<ResourceHandlerRegistrationCustomizer> 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<WebMvcConfigurer> webMvcConfigurers) {
if (webMvcConfigurers != null) {
for (WebMvcConfigurer webMvcConfigurer : webMvcConfigurers) {
Validator validator = webMvcConfigurer.getValidator();
if (validator != null) {
return validator;
}
}
}
return null;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> 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 <T> Collection<T> getBeansOfType(Class<T> 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 new SpringValidatorAdapterWrapper(
new SpringValidatorAdapter(validator), false);
return this.userDefinedValidator;
}
}
else {
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);
}
}
}
}

@ -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 {

Loading…
Cancel
Save