From 551b7cd60e20d97d8fff7baf96a64e0c73542fb4 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 10 Jun 2016 16:06:12 -0400 Subject: [PATCH] Add global Validator bean to WebReactiveConfiguration --- spring-web-reactive/build.gradle | 1 + .../config/WebReactiveConfiguration.java | 58 +++++++++++++++++++ .../RequestMappingHandlerAdapter.java | 23 +++++++- .../config/WebReactiveConfigurationTests.java | 8 ++- 4 files changed, 87 insertions(+), 3 deletions(-) diff --git a/spring-web-reactive/build.gradle b/spring-web-reactive/build.gradle index ab52dcf29a..15b2b0ede0 100644 --- a/spring-web-reactive/build.gradle +++ b/spring-web-reactive/build.gradle @@ -111,6 +111,7 @@ dependencies { optional "org.eclipse.jetty:jetty-servlet:${jettyVersion}" optional("org.freemarker:freemarker:2.3.23") optional("com.fasterxml:aalto-xml:1.0.0") + optional("javax.validation:validation-api:1.0.0.GA") provided "javax.servlet:javax.servlet-api:3.1.0" diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java index ee727228a6..e30c1e4fc3 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java @@ -22,6 +22,8 @@ import java.util.Map; import reactor.core.converter.DependencyUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.BeanInitializationException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; @@ -48,6 +50,8 @@ import org.springframework.http.converter.reactive.CodecHttpMessageConverter; import org.springframework.http.converter.reactive.HttpMessageConverter; import org.springframework.http.converter.reactive.ResourceHttpMessageConverter; import org.springframework.util.ClassUtils; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.reactive.result.SimpleHandlerAdapter; @@ -185,6 +189,7 @@ public class WebReactiveConfiguration implements ApplicationContextAware { adapter.setMessageConverters(getMessageConverters()); adapter.setConversionService(mvcConversionService()); + adapter.setValidator(mvcValidator()); return adapter; } @@ -284,6 +289,46 @@ public class WebReactiveConfiguration implements ApplicationContextAware { } } + /** + * Return a global {@link Validator} instance for example for validating + * {@code @RequestBody} method arguments. + *

Delegates to {@link #getValidator()} first. If that returns {@code null} + * checks the classpath for the presence of a JSR-303 implementations + * before creating a {@code OptionalValidatorFactoryBean}. If a JSR-303 + * implementation is not available, a "no-op" {@link Validator} is returned. + */ + @Bean + public Validator mvcValidator() { + Validator validator = getValidator(); + if (validator == null) { + if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { + Class clazz; + try { + String name = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean"; + clazz = ClassUtils.forName(name, classLoader); + } + catch (ClassNotFoundException ex) { + throw new BeanInitializationException("Could not find default validator class", ex); + } + catch (LinkageError ex) { + throw new BeanInitializationException("Could not load default validator class", ex); + } + validator = (Validator) BeanUtils.instantiate(clazz); + } + else { + validator = new NoOpValidator(); + } + } + return validator; + } + + /** + * Override this method to provide a custom {@link Validator}. + */ + protected Validator getValidator() { + return null; + } + @Bean public SimpleHandlerAdapter simpleHandlerAdapter() { return new SimpleHandlerAdapter(); @@ -317,4 +362,17 @@ public class WebReactiveConfiguration implements ApplicationContextAware { protected void configureViewResolvers(ViewResolverRegistry registry) { } + + private static final class NoOpValidator implements Validator { + + @Override + public boolean supports(Class clazz) { + return false; + } + + @Override + public void validate(Object target, Errors errors) { + } + } + } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java index 2cbc579437..dba37ad1f8 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java @@ -39,7 +39,7 @@ import org.springframework.http.converter.reactive.CodecHttpMessageConverter; import org.springframework.http.converter.reactive.HttpMessageConverter; import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.ModelMap; -import org.springframework.util.ObjectUtils; +import org.springframework.validation.Validator; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver; import org.springframework.web.reactive.HandlerAdapter; @@ -67,6 +67,8 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory private ConversionService conversionService = new DefaultFormattingConversionService(); + private Validator validator; + private ConfigurableBeanFactory beanFactory; private final Map, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64); @@ -141,6 +143,23 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory return this.conversionService; } + /** + * Configure a Validator for validation of controller method arguments such + * as {@code @RequestBody}. + * + * TODO: this may be replaced by DataBinder + */ + public void setValidator(Validator validator) { + this.validator = validator; + } + + /** + * Return the configured Validator. + */ + public Validator getValidator() { + return this.validator; + } + /** * A {@link ConfigurableBeanFactory} is expected for resolving expressions * in method argument default values. @@ -173,7 +192,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver(cs, getBeanFactory())); resolvers.add(new PathVariableMapMethodArgumentResolver()); - resolvers.add(new RequestBodyArgumentResolver(getMessageConverters(), cs)); + resolvers.add(new RequestBodyArgumentResolver(getMessageConverters(), cs, getValidator())); resolvers.add(new RequestHeaderMethodArgumentResolver(cs, getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new CookieValueMethodArgumentResolver(cs, getBeanFactory())); diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/WebReactiveConfigurationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/WebReactiveConfigurationTests.java index 9e39c8e588..43578f2656 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/WebReactiveConfigurationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/WebReactiveConfigurationTests.java @@ -20,7 +20,6 @@ import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; - import javax.xml.bind.annotation.XmlRootElement; import org.junit.Before; @@ -50,6 +49,8 @@ import org.springframework.http.server.reactive.MockServerHttpRequest; import org.springframework.http.server.reactive.MockServerHttpResponse; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; +import org.springframework.validation.Validator; +import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; @@ -148,6 +149,11 @@ public class WebReactiveConfigurationTests { name = "mvcConversionService"; ConversionService service = context.getBean(name, ConversionService.class); assertSame(service, adapter.getConversionService()); + + name = "mvcValidator"; + Validator validator = context.getBean(name, Validator.class); + assertSame(validator, adapter.getValidator()); + assertEquals(OptionalValidatorFactoryBean.class, validator.getClass()); } @Test