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
This commit is contained in:
@@ -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 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user