Reuse auto-configured `Validator` in WebFlux

This commit makes sure that if a `Validator` is auto-configured, it is
reused as the `webFluxValidator`. This is pretty much the same thing as
what was done recently for Spring MVC.

Since the infrastructure is now shared, the package protected class has
been published in the `.validation` package.

Closes gh-8400
This commit is contained in:
Stephane Nicoll 2017-02-28 15:49:21 +01:00
parent deaa6089b0
commit cf64d9fd35
6 changed files with 169 additions and 29 deletions

View File

@ -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);
}

View File

@ -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());
}

View File

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

View File

@ -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<String, Object>(),
@ -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);
}
}

View File

@ -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));

View File

@ -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<Validator> getValidator() {
return Optional.of(this.validator);
}
}
@Configuration
protected static class WebFluxJsr303Validator implements WebFluxConfigurer {
private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
@Override
public Optional<Validator> 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();
}
}
}