From 3533024ab87468a9edcd95e762458e6af9516ba9 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 13 Oct 2016 20:55:42 +0200 Subject: [PATCH 1/4] Add EnableWebReactive + WebReactiveConfigurer This commit improves the existing web reactive configuration infrastructure with the following changes: * renamed `WebReactiveConfiguration` to `WebReactiveConfigurationSupport` and is is no longer a Configuration class * created the `WebReactiveConfigurer` interface; Configuration classes implementing it will augment the web reactive configuration support * created the `DelegatingWebReactiveConfiguration` and `WebReactiveConfigurerComposite` to effectively tie those custom-defined configurers to the main configuration support * created the `@EnableWebReactive` to active that support in configuration classes Issue: SPR-14754 --- .../DelegatingWebReactiveConfiguration.java | 115 +++++++++++++ .../reactive/config/EnableWebReactive.java | 80 +++++++++ ...a => WebReactiveConfigurationSupport.java} | 15 +- .../config/WebReactiveConfigurer.java | 156 ++++++++++++++++++ .../WebReactiveConfigurerComposite.java | 152 +++++++++++++++++ ...legatingWebReactiveConfigurationTests.java | 128 ++++++++++++++ ...WebReactiveConfigurationSupportTests.java} | 30 ++-- .../DispatcherHandlerIntegrationTests.java | 4 +- ...CrossOriginAnnotationIntegrationTests.java | 5 +- .../GlobalCorsConfigIntegrationTests.java | 4 +- .../JacksonHintsIntegrationTests.java | 5 +- ...pingExceptionHandlingIntegrationTests.java | 5 +- .../RequestMappingIntegrationTests.java | 5 +- ...pingMessageConversionIntegrationTests.java | 12 +- ...MappingViewResolutionIntegrationTests.java | 4 +- .../annotation/SseIntegrationTests.java | 5 +- 16 files changed, 683 insertions(+), 42 deletions(-) create mode 100644 spring-web-reactive/src/main/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfiguration.java create mode 100644 spring-web-reactive/src/main/java/org/springframework/web/reactive/config/EnableWebReactive.java rename spring-web-reactive/src/main/java/org/springframework/web/reactive/config/{WebReactiveConfiguration.java => WebReactiveConfigurationSupport.java} (98%) create mode 100644 spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java create mode 100644 spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java create mode 100644 spring-web-reactive/src/test/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfigurationTests.java rename spring-web-reactive/src/test/java/org/springframework/web/reactive/config/{WebReactiveConfigurationTests.java => WebReactiveConfigurationSupportTests.java} (94%) diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfiguration.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfiguration.java new file mode 100644 index 00000000000..4636f358c13 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfiguration.java @@ -0,0 +1,115 @@ +package org.springframework.web.reactive.config; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.util.CollectionUtils; +import org.springframework.validation.MessageCodesResolver; +import org.springframework.validation.Validator; +import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; +import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; + +/** + * A subclass of {@code WebReactiveConfigurationSupport} that detects and delegates + * to all beans of type {@link WebReactiveConfigurer} allowing them to customize the + * configuration provided by {@code WebReactiveConfigurationSupport}. This is the + * class actually imported by {@link EnableWebReactive @EnableWebReactive}. + * + * @author Brian Clozel + * @since 5.0 + */ +@Configuration +public class DelegatingWebReactiveConfiguration extends WebReactiveConfigurationSupport { + + private final WebReactiveConfigurerComposite configurers = new WebReactiveConfigurerComposite(); + + @Autowired(required = false) + public void setConfigurers(List configurers) { + if (!CollectionUtils.isEmpty(configurers)) { + this.configurers.addWebReactiveConfigurers(configurers); + } + } + + @Override + protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { + return this.configurers.createRequestMappingHandlerMapping() + .orElse(super.createRequestMappingHandlerMapping()); + } + + @Override + protected void configureRequestedContentTypeResolver(RequestedContentTypeResolverBuilder builder) { + this.configurers.configureRequestedContentTypeResolver(builder); + } + + @Override + protected void addCorsMappings(CorsRegistry registry) { + this.configurers.addCorsMappings(registry); + } + + @Override + public void configurePathMatching(PathMatchConfigurer configurer) { + this.configurers.configurePathMatching(configurer); + } + + @Override + protected void addResourceHandlers(ResourceHandlerRegistry registry) { + this.configurers.addResourceHandlers(registry); + } + + @Override + protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() { + return this.configurers.createRequestMappingHandlerAdapter() + .orElse(super.createRequestMappingHandlerAdapter()); + } + + @Override + protected void addArgumentResolvers(List resolvers) { + this.configurers.addArgumentResolvers(resolvers); + } + + @Override + protected void configureMessageReaders(List> messageReaders) { + this.configurers.configureMessageReaders(messageReaders); + } + + @Override + protected void extendMessageReaders(List> messageReaders) { + this.configurers.extendMessageReaders(messageReaders); + } + + @Override + protected void addFormatters(FormatterRegistry registry) { + this.configurers.addFormatters(registry); + } + + @Override + protected Validator getValidator() { + return this.configurers.getValidator().orElse(super.getValidator()); + } + + @Override + protected MessageCodesResolver getMessageCodesResolver() { + return this.configurers.getMessageCodesResolver().orElse(super.getMessageCodesResolver()); + } + + @Override + protected void configureMessageWriters(List> messageWriters) { + this.configurers.configureMessageWriters(messageWriters); + } + + @Override + protected void extendMessageWriters(List> messageWriters) { + this.configurers.extendMessageWriters(messageWriters); + } + + @Override + protected void configureViewResolvers(ViewResolverRegistry registry) { + this.configurers.configureViewResolvers(registry); + } +} diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/EnableWebReactive.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/EnableWebReactive.java new file mode 100644 index 00000000000..3a11ea5bf19 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/EnableWebReactive.java @@ -0,0 +1,80 @@ +package org.springframework.web.reactive.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +/** + * Adding this annotation to an {@code @Configuration} class imports the Spring Web + * Reactive configuration from {@link WebReactiveConfigurationSupport}, e.g.: + * + *
+ * @Configuration
+ * @EnableWebReactive
+ * @ComponentScan(basePackageClasses = { MyConfiguration.class })
+ * public class MyWebConfiguration {
+ *
+ * }
+ * 
+ * + *

To customize the imported configuration, implement the interface + * {@link WebReactiveConfigurer} and override individual methods, e.g.: + * + *

+ * @Configuration
+ * @EnableWebMvc
+ * @ComponentScan(basePackageClasses = { MyConfiguration.class })
+ * public class MyConfiguration implements WebReactiveConfigurer {
+ *
+ * 	   @Override
+ * 	   public void addFormatters(FormatterRegistry formatterRegistry) {
+ *         formatterRegistry.addConverter(new MyConverter());
+ * 	   }
+ *
+ * 	   @Override
+ * 	   public void configureMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
+ *         messageWriters.add(new MyHttpMessageWriter());
+ * 	   }
+ *
+ *     // More overridden methods ...
+ * }
+ * 
+ * + *

If {@link WebReactiveConfigurer} does not expose some advanced setting that + * needs to be configured, consider removing the {@code @EnableWebReactive} + * annotation and extending directly from {@link WebReactiveConfigurationSupport} + * or {@link DelegatingWebReactiveConfiguration}, e.g.: + * + *

+ * @Configuration
+ * @ComponentScan(basePackageClasses = { MyConfiguration.class })
+ * public class MyConfiguration extends WebReactiveConfigurationSupport {
+ *
+ * 	   @Override
+ *	   public void addFormatters(FormatterRegistry formatterRegistry) {
+ *         formatterRegistry.addConverter(new MyConverter());
+ *	   }
+ *
+ *	   @Bean
+ *	   public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
+ *         // Create or delegate to "super" to create and
+ *         // customize properties of RequestMappingHandlerAdapter
+ *	   }
+ * }
+ * 
+ * + * @author Brian Clozel + * @since 5.0 + * @see WebReactiveConfigurer + * @see WebReactiveConfigurationSupport + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Import(DelegatingWebReactiveConfiguration.class) +public @interface EnableWebReactive { +} 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/WebReactiveConfigurationSupport.java similarity index 98% rename from spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java rename to spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurationSupport.java index 9a55e2e435e..174e800f737 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/WebReactiveConfigurationSupport.java @@ -28,7 +28,6 @@ import org.springframework.beans.factory.BeanInitializationException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.core.codec.ByteBufferDecoder; import org.springframework.core.codec.ByteBufferEncoder; import org.springframework.core.codec.CharSequenceEncoder; @@ -82,17 +81,16 @@ import org.springframework.web.server.ServerWebExchange; * @author Rossen Stoyanchev * @since 5.0 */ -@Configuration -public class WebReactiveConfiguration implements ApplicationContextAware { +public class WebReactiveConfigurationSupport implements ApplicationContextAware { private static final boolean jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", - WebReactiveConfiguration.class.getClassLoader()) && + WebReactiveConfigurationSupport.class.getClassLoader()) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", - WebReactiveConfiguration.class.getClassLoader()); + WebReactiveConfigurationSupport.class.getClassLoader()); private static final boolean jaxb2Present = - ClassUtils.isPresent("javax.xml.bind.Binder", WebReactiveConfiguration.class.getClassLoader()); + ClassUtils.isPresent("javax.xml.bind.Binder", WebReactiveConfigurationSupport.class.getClassLoader()); private Map corsConfigurations; @@ -234,7 +232,6 @@ public class WebReactiveConfiguration implements ApplicationContextAware { if (pathMatchConfigurer.getPathHelper() != null) { handlerMapping.setPathHelper(pathMatchConfigurer.getPathHelper()); } - } else { handlerMapping = new EmptyHandlerMapping(); @@ -464,6 +461,7 @@ public class WebReactiveConfiguration implements ApplicationContextAware { writers.add(new ServerSentEventHttpMessageWriter(sseDataEncoders)); } } + /** * Override this to modify the list of message writers after it has been * configured, for example to add some in addition to the default ones. @@ -484,7 +482,8 @@ public class WebReactiveConfiguration implements ApplicationContextAware { } /** - * Override this to configure view resolution. + * Configure view resolution for supporting template engines. + * @see ViewResolverRegistry */ protected void configureViewResolvers(ViewResolverRegistry registry) { } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java new file mode 100644 index 00000000000..645e6d0bb30 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java @@ -0,0 +1,156 @@ +package org.springframework.web.reactive.config; + +import java.util.List; +import java.util.Optional; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.format.Formatter; +import org.springframework.format.FormatterRegistry; +import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.validation.MessageCodesResolver; +import org.springframework.validation.Validator; +import org.springframework.web.reactive.accept.CompositeContentTypeResolver; +import org.springframework.web.reactive.accept.RequestedContentTypeResolver; +import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; +import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; + +/** + * Defines callback methods to customize the configuration for Web Reactive + * applications enabled via {@code @EnableWebReactive}. + * + *

{@code @EnableWebReactive}-annotated configuration classes may implement + * this interface to be called back and given a chance to customize the + * default configuration. Consider implementing this interface and + * overriding the relevant methods for your needs. + * + * @author Brian Clozel + * @since 5.0 + */ +public interface WebReactiveConfigurer { + + /** + * Provide a custom sub-class of {@link RequestMappingHandlerMapping} + * instead of the one created by default. + * The default implementation returns {@code Optional.empty()}. + */ + default Optional createRequestMappingHandlerMapping() { + return Optional.empty(); + } + + /** + * Configure how the requested content type is resolved. + *

The given builder will create a composite of multiple + * {@link RequestedContentTypeResolver}s, each defining a way to resolve the + * the requested content type (accept HTTP header, path extension, parameter, etc). + * @param builder factory that creates a {@link CompositeContentTypeResolver} instance + */ + default void configureRequestedContentTypeResolver(RequestedContentTypeResolverBuilder builder) { + } + + /** + * Configure cross origin requests processing. + * @see CorsRegistry + */ + default void addCorsMappings(CorsRegistry registry) { + } + + /** + * Configure path matching options. + *

The given configurer assists with configuring + * {@code HandlerMapping}s with path matching options. + * @param configurer the {@link PathMatchConfigurer} instance + */ + default void configurePathMatching(PathMatchConfigurer configurer) { + } + + /** + * Add resource handlers for serving static resources. + * @see ResourceHandlerRegistry + */ + default void addResourceHandlers(ResourceHandlerRegistry registry) { + } + + /** + * Provide a custom sub-class of {@link RequestMappingHandlerAdapter} + * instead of the one created by default. + * The default implementation returns {@code Optional.empty()}. + */ + default Optional createRequestMappingHandlerAdapter() { + return Optional.empty(); + } + + /** + * Provide custom argument resolvers without overriding the built-in ones. + * @param resolvers a list of resolvers to add to the built-in ones + */ + default void addArgumentResolvers(List resolvers) { + } + + /** + * Configure the message readers to use for decoding controller method arguments. + *

If no message readers are specified, default readers will be added via + * {@link WebReactiveConfigurationSupport#addDefaultHttpMessageReaders}. + * @param messageReaders a list to add message readers to, initially an empty list + */ + default void configureMessageReaders(List> messageReaders) { + } + + /** + * Modify the list of message readers to use for decoding controller method arguments, + * for example to add some in addition to the ones already configured. + */ + default void extendMessageReaders(List> messageReaders) { + } + + /** + * Add custom {@link Converter}s and {@link Formatter}s. + */ + default void addFormatters(FormatterRegistry registry) { + } + + /** + * Provide a custom {@link Validator}, instead of the instance configured by default. + *

Only a single instance is allowed, an error will be thrown if multiple + * {@code Validator}s are returned by {@code WebReactiveConfigurer}s. + * The default implementation returns {@code Optional.empty()}. + */ + default Optional getValidator() { + return Optional.empty(); + } + + /** + * Provide a custom {@link MessageCodesResolver}, instead of using the one + * provided by {@link org.springframework.validation.DataBinder} instances. + * The default implementation returns {@code Optional.empty()}. + */ + default Optional getMessageCodesResolver() { + return Optional.empty(); + } + + /** + * Configure the message writers to use for encoding return values. + *

If no message writers are specified, default writers will be added via + * {@link WebReactiveConfigurationSupport#addDefaultHttpMessageWriters(List)}. + * @param messageWriters a list to add message writers to, initially an empty list + */ + default void configureMessageWriters(List> messageWriters) { + } + + /** + * Modify the list of message writers to use for encoding return values, + * for example to add some in addition to the ones already configured. + */ + default void extendMessageWriters(List> messageWriters) { + } + + /** + * Configure view resolution for supporting template engines. + * @see ViewResolverRegistry + */ + default void configureViewResolvers(ViewResolverRegistry registry) { + } + +} diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java new file mode 100644 index 00000000000..0e5979e98a5 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java @@ -0,0 +1,152 @@ +package org.springframework.web.reactive.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.springframework.format.FormatterRegistry; +import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.util.CollectionUtils; +import org.springframework.validation.MessageCodesResolver; +import org.springframework.validation.Validator; +import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; +import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; + +/** + * A {@link WebReactiveConfigurer} that delegates to one or more others. + * + * @author Brian Clozel + * @since 5.0 + */ +public class WebReactiveConfigurerComposite implements WebReactiveConfigurer { + + private final List delegates = new ArrayList<>(); + + public void addWebReactiveConfigurers(List configurers) { + if (!CollectionUtils.isEmpty(configurers)) { + this.delegates.addAll(configurers); + } + } + + @Override + public Optional createRequestMappingHandlerMapping() { + Optional selected = Optional.empty(); + for (WebReactiveConfigurer configurer : this.delegates) { + Optional handlerMapping = configurer.createRequestMappingHandlerMapping(); + if (handlerMapping.isPresent()) { + if (selected != null) { + throw new IllegalStateException("No unique RequestMappingHandlerMapping found: {" + + selected.get() + ", " + handlerMapping.get() + "}"); + } + selected = handlerMapping; + } + } + return selected; + } + + @Override + public void configureRequestedContentTypeResolver(RequestedContentTypeResolverBuilder builder) { + this.delegates.stream().forEach(delegate -> delegate.configureRequestedContentTypeResolver(builder)); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + this.delegates.stream().forEach(delegate -> delegate.addCorsMappings(registry)); + } + + @Override + public void configurePathMatching(PathMatchConfigurer configurer) { + this.delegates.stream().forEach(delegate -> delegate.configurePathMatching(configurer)); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + this.delegates.stream().forEach(delegate -> delegate.addResourceHandlers(registry)); + } + + @Override + public Optional createRequestMappingHandlerAdapter() { + Optional selected = Optional.empty(); + for (WebReactiveConfigurer configurer : this.delegates) { + Optional handlerAdapter = configurer.createRequestMappingHandlerAdapter(); + if (handlerAdapter.isPresent()) { + if (selected != null) { + throw new IllegalStateException("No unique RequestMappingHandlerAdapter found: {" + + selected.get() + ", " + handlerAdapter.get() + "}"); + } + selected = handlerAdapter; + } + } + return selected; + } + + @Override + public void addArgumentResolvers(List resolvers) { + this.delegates.stream().forEach(delegate -> delegate.addArgumentResolvers(resolvers)); + } + + @Override + public void configureMessageReaders(List> messageReaders) { + this.delegates.stream().forEach(delegate -> delegate.configureMessageReaders(messageReaders)); + } + + @Override + public void extendMessageReaders(List> messageReaders) { + this.delegates.stream().forEach(delegate -> delegate.extendMessageReaders(messageReaders)); + } + + @Override + public void addFormatters(FormatterRegistry registry) { + this.delegates.stream().forEach(delegate -> delegate.addFormatters(registry)); + } + + @Override + public Optional getValidator() { + Optional selected = Optional.empty(); + for (WebReactiveConfigurer configurer : this.delegates) { + Optional validator = configurer.getValidator(); + if (validator.isPresent()) { + if (selected != null) { + throw new IllegalStateException("No unique Validator found: {" + + selected.get() + ", " + validator.get() + "}"); + } + selected = validator; + } + } + return selected; + } + + @Override + public Optional getMessageCodesResolver() { + Optional selected = Optional.empty(); + for (WebReactiveConfigurer configurer : this.delegates) { + Optional messageCodesResolver = configurer.getMessageCodesResolver(); + if (messageCodesResolver.isPresent()) { + if (selected != null) { + throw new IllegalStateException("No unique MessageCodesResolver found: {" + + selected.get() + ", " + messageCodesResolver.get() + "}"); + } + selected = messageCodesResolver; + } + } + return selected; + } + + @Override + public void configureMessageWriters(List> messageWriters) { + this.delegates.stream().forEach(delegate -> delegate.configureMessageWriters(messageWriters)); + } + + @Override + public void extendMessageWriters(List> messageWriters) { + this.delegates.stream().forEach(delegate -> delegate.extendMessageWriters(messageWriters)); + } + + @Override + public void configureViewResolvers(ViewResolverRegistry registry) { + this.delegates.stream().forEach(delegate -> delegate.configureViewResolvers(registry)); + } +} diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfigurationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfigurationTests.java new file mode 100644 index 00000000000..49c2c32e4ce --- /dev/null +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfigurationTests.java @@ -0,0 +1,128 @@ +package org.springframework.web.reactive.config; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.convert.ConversionService; +import org.springframework.format.FormatterRegistry; +import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; +import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; + +/** + * Test fixture for {@link DelegatingWebReactiveConfiguration} tests. + * + * @author Brian Clozel + */ +public class DelegatingWebReactiveConfigurationTests { + + private DelegatingWebReactiveConfiguration delegatingConfig; + + @Mock + private WebReactiveConfigurer webReactiveConfigurer; + + @Captor + private ArgumentCaptor>> readers; + + @Captor + private ArgumentCaptor>> writers; + + @Captor + private ArgumentCaptor formatterRegistry; + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + delegatingConfig = new DelegatingWebReactiveConfiguration(); + delegatingConfig.setApplicationContext(new StaticApplicationContext()); + given(webReactiveConfigurer.createRequestMappingHandlerMapping()).willReturn(Optional.empty()); + given(webReactiveConfigurer.createRequestMappingHandlerAdapter()).willReturn(Optional.empty()); + given(webReactiveConfigurer.getValidator()).willReturn(Optional.empty()); + given(webReactiveConfigurer.getMessageCodesResolver()).willReturn(Optional.empty()); + } + + @Test + public void requestMappingHandlerAdapter() throws Exception { + delegatingConfig.setConfigurers(Collections.singletonList(webReactiveConfigurer)); + RequestMappingHandlerAdapter adapter = delegatingConfig.requestMappingHandlerAdapter(); + + ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer(); + ConversionService initializerConversionService = initializer.getConversionService(); + assertTrue(initializer.getValidator() instanceof LocalValidatorFactoryBean); + + verify(webReactiveConfigurer).createRequestMappingHandlerAdapter(); + verify(webReactiveConfigurer).configureMessageReaders(readers.capture()); + verify(webReactiveConfigurer).extendMessageReaders(readers.capture()); + verify(webReactiveConfigurer).getValidator(); + verify(webReactiveConfigurer).getMessageCodesResolver(); + verify(webReactiveConfigurer).addFormatters(formatterRegistry.capture()); + verify(webReactiveConfigurer).addArgumentResolvers(any()); + + assertSame(formatterRegistry.getValue(), initializerConversionService); + assertEquals(5, readers.getValue().size()); + } + + @Test + public void requestMappingHandlerMapping() throws Exception { + delegatingConfig.setConfigurers(Collections.singletonList(webReactiveConfigurer)); + delegatingConfig.requestMappingHandlerMapping(); + + verify(webReactiveConfigurer).createRequestMappingHandlerMapping(); + verify(webReactiveConfigurer).configureRequestedContentTypeResolver(any(RequestedContentTypeResolverBuilder.class)); + verify(webReactiveConfigurer).addCorsMappings(any(CorsRegistry.class)); + verify(webReactiveConfigurer).configurePathMatching(any(PathMatchConfigurer.class)); + } + + @Test + public void resourceHandlerMapping() throws Exception { + delegatingConfig.setConfigurers(Collections.singletonList(webReactiveConfigurer)); + doAnswer(invocation -> { + ResourceHandlerRegistry registry = invocation.getArgumentAt(0, ResourceHandlerRegistry.class); + registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static"); + return null; + }).when(webReactiveConfigurer).addResourceHandlers(any(ResourceHandlerRegistry.class)); + + delegatingConfig.resourceHandlerMapping(); + verify(webReactiveConfigurer).addResourceHandlers(any(ResourceHandlerRegistry.class)); + verify(webReactiveConfigurer).configurePathMatching(any(PathMatchConfigurer.class)); + } + + @Test + public void responseBodyResultHandler() throws Exception { + delegatingConfig.setConfigurers(Collections.singletonList(webReactiveConfigurer)); + delegatingConfig.responseBodyResultHandler(); + + verify(webReactiveConfigurer).configureMessageWriters(writers.capture()); + verify(webReactiveConfigurer).extendMessageWriters(writers.capture()); + verify(webReactiveConfigurer).configureRequestedContentTypeResolver(any(RequestedContentTypeResolverBuilder.class)); + } + + @Test + public void viewResolutionResultHandler() throws Exception { + delegatingConfig.setConfigurers(Collections.singletonList(webReactiveConfigurer)); + delegatingConfig.viewResolutionResultHandler(); + + verify(webReactiveConfigurer).configureViewResolvers(any(ViewResolverRegistry.class)); + } +} 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/WebReactiveConfigurationSupportTests.java similarity index 94% rename from spring-web-reactive/src/test/java/org/springframework/web/reactive/config/WebReactiveConfigurationTests.java rename to spring-web-reactive/src/test/java/org/springframework/web/reactive/config/WebReactiveConfigurationSupportTests.java index 122e51ce923..bd470960b31 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/WebReactiveConfigurationSupportTests.java @@ -80,10 +80,10 @@ import static org.springframework.http.MediaType.IMAGE_PNG; import static org.springframework.http.MediaType.TEXT_PLAIN; /** - * Unit tests for {@link WebReactiveConfiguration}. + * Unit tests for {@link WebReactiveConfigurationSupport}. * @author Rossen Stoyanchev */ -public class WebReactiveConfigurationTests { +public class WebReactiveConfigurationSupportTests { private MockServerHttpRequest request; @@ -100,7 +100,7 @@ public class WebReactiveConfigurationTests { @Test public void requestMappingHandlerMapping() throws Exception { - ApplicationContext context = loadConfig(WebReactiveConfiguration.class); + ApplicationContext context = loadConfig(WebReactiveConfig.class); String name = "requestMappingHandlerMapping"; RequestMappingHandlerMapping mapping = context.getBean(name, RequestMappingHandlerMapping.class); @@ -138,10 +138,10 @@ public class WebReactiveConfigurationTests { @Test public void requestMappingHandlerAdapter() throws Exception { - ApplicationContext context = loadConfig(WebReactiveConfiguration.class); + ApplicationContext context = loadConfig(WebReactiveConfig.class); String name = "requestMappingHandlerAdapter"; - RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class); + RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class); assertNotNull(adapter); List> readers = adapter.getMessageReaders(); @@ -185,7 +185,7 @@ public class WebReactiveConfigurationTests { @Test public void responseEntityResultHandler() throws Exception { - ApplicationContext context = loadConfig(WebReactiveConfiguration.class); + ApplicationContext context = loadConfig(WebReactiveConfig.class); String name = "responseEntityResultHandler"; ResponseEntityResultHandler handler = context.getBean(name, ResponseEntityResultHandler.class); @@ -210,7 +210,7 @@ public class WebReactiveConfigurationTests { @Test public void responseBodyResultHandler() throws Exception { - ApplicationContext context = loadConfig(WebReactiveConfiguration.class); + ApplicationContext context = loadConfig(WebReactiveConfig.class); String name = "responseBodyResultHandler"; ResponseBodyResultHandler handler = context.getBean(name, ResponseBodyResultHandler.class); @@ -262,7 +262,7 @@ public class WebReactiveConfigurationTests { AbstractHandlerMapping handlerMapping = context.getBean(name, AbstractHandlerMapping.class); assertNotNull(handlerMapping); - assertEquals(Ordered.LOWEST_PRECEDENCE -1, handlerMapping.getOrder()); + assertEquals(Ordered.LOWEST_PRECEDENCE - 1, handlerMapping.getOrder()); assertNotNull(handlerMapping.getPathHelper()); assertNotNull(handlerMapping.getPathMatcher()); @@ -296,9 +296,12 @@ public class WebReactiveConfigurationTests { return context; } + @EnableWebReactive + static class WebReactiveConfig { + } @Configuration - static class CustomPatchMatchConfig extends WebReactiveConfiguration { + static class CustomPatchMatchConfig extends WebReactiveConfigurationSupport { @Override public void configurePathMatching(PathMatchConfigurer configurer) { @@ -308,7 +311,7 @@ public class WebReactiveConfigurationTests { } @Configuration - static class CustomMessageConverterConfig extends WebReactiveConfiguration { + static class CustomMessageConverterConfig extends WebReactiveConfigurationSupport { @Override protected void configureMessageReaders(List> messageReaders) { @@ -331,8 +334,9 @@ public class WebReactiveConfigurationTests { } } - @Configuration @SuppressWarnings("unused") - static class CustomViewResolverConfig extends WebReactiveConfiguration { + @Configuration + @SuppressWarnings("unused") + static class CustomViewResolverConfig extends WebReactiveConfigurationSupport { @Override protected void configureViewResolvers(ViewResolverRegistry registry) { @@ -348,7 +352,7 @@ public class WebReactiveConfigurationTests { } @Configuration - static class CustomResourceHandlingConfig extends WebReactiveConfiguration { + static class CustomResourceHandlingConfig extends WebReactiveConfigurationSupport { @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DispatcherHandlerIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DispatcherHandlerIntegrationTests.java index c1eecceb4c1..7f346eddfca 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DispatcherHandlerIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DispatcherHandlerIntegrationTests.java @@ -43,7 +43,7 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.reactive.HandlerAdapter; import org.springframework.web.reactive.HandlerMapping; -import org.springframework.web.reactive.config.WebReactiveConfiguration; +import org.springframework.web.reactive.config.WebReactiveConfigurationSupport; import org.springframework.web.reactive.function.support.HandlerFunctionAdapter; import org.springframework.web.reactive.function.support.ResponseResultHandler; import org.springframework.web.reactive.result.view.ViewResolver; @@ -104,7 +104,7 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr @Configuration - static class TestConfiguration extends WebReactiveConfiguration { + static class TestConfiguration extends WebReactiveConfigurationSupport { @Bean public PersonHandler personHandler() { diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java index fcf44e7c5ff..3e86bd6eb41 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java @@ -39,7 +39,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import org.springframework.web.reactive.config.WebReactiveConfiguration; +import org.springframework.web.reactive.config.EnableWebReactive; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -222,9 +222,10 @@ public class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappin @Configuration + @EnableWebReactive @ComponentScan(resourcePattern = "**/CrossOriginAnnotationIntegrationTests*") @SuppressWarnings({"unused", "WeakerAccess"}) - static class WebConfig extends WebReactiveConfiguration { + static class WebConfig { } @RestController @SuppressWarnings("unused") diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java index ce4dc61b6a2..0cc0c176783 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java @@ -32,7 +32,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.config.CorsRegistry; -import org.springframework.web.reactive.config.WebReactiveConfiguration; +import org.springframework.web.reactive.config.WebReactiveConfigurationSupport; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -137,7 +137,7 @@ public class GlobalCorsConfigIntegrationTests extends AbstractRequestMappingInte @Configuration @ComponentScan(resourcePattern = "**/GlobalCorsConfigIntegrationTests*.class") @SuppressWarnings({"unused", "WeakerAccess"}) - static class WebConfig extends WebReactiveConfiguration { + static class WebConfig extends WebReactiveConfigurationSupport { @Override protected void addCorsMappings(CorsRegistry registry) { diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/JacksonHintsIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/JacksonHintsIntegrationTests.java index b3c62b5b7aa..3f56fe1b50a 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/JacksonHintsIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/JacksonHintsIntegrationTests.java @@ -34,7 +34,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.config.WebReactiveConfiguration; +import org.springframework.web.reactive.config.EnableWebReactive; /** * @author Sebastien Deleuze @@ -93,8 +93,9 @@ public class JacksonHintsIntegrationTests extends AbstractRequestMappingIntegrat @Configuration @ComponentScan(resourcePattern = "**/JacksonHintsIntegrationTests*.class") + @EnableWebReactive @SuppressWarnings({"unused", "WeakerAccess"}) - static class WebConfig extends WebReactiveConfiguration { + static class WebConfig { } @RestController diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java index 04758fd8cc6..9c1bedc2049 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java @@ -28,7 +28,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.config.WebReactiveConfiguration; +import org.springframework.web.reactive.config.EnableWebReactive; import static org.junit.Assert.assertEquals; @@ -64,9 +64,10 @@ public class RequestMappingExceptionHandlingIntegrationTests extends AbstractReq @Configuration + @EnableWebReactive @ComponentScan(resourcePattern = "**/RequestMappingExceptionHandlingIntegrationTests$*.class") @SuppressWarnings({"unused", "WeakerAccess"}) - static class WebConfig extends WebReactiveConfiguration { + static class WebConfig { } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java index 733a0da8df0..3140a064832 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java @@ -29,7 +29,7 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.config.WebReactiveConfiguration; +import org.springframework.web.reactive.config.EnableWebReactive; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -74,9 +74,10 @@ public class RequestMappingIntegrationTests extends AbstractRequestMappingIntegr @Configuration + @EnableWebReactive @ComponentScan(resourcePattern = "**/RequestMappingIntegrationTests$*.class") @SuppressWarnings({"unused", "WeakerAccess"}) - static class WebConfig extends WebReactiveConfiguration { + static class WebConfig { } @RestController diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java index 589f29fbb9e..3d792664aab 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java @@ -56,11 +56,12 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.config.WebReactiveConfiguration; +import org.springframework.web.reactive.config.EnableWebReactive; -import static java.util.Arrays.*; -import static org.junit.Assert.*; -import static org.springframework.http.MediaType.*; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.http.MediaType.APPLICATION_XML; /** * {@code @RequestMapping} integration tests focusing on serialization and @@ -371,9 +372,10 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq @Configuration + @EnableWebReactive @ComponentScan(resourcePattern = "**/RequestMappingMessageConversionIntegrationTests$*.class") @SuppressWarnings({"unused", "WeakerAccess"}) - static class WebConfig extends WebReactiveConfiguration { + static class WebConfig { } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingViewResolutionIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingViewResolutionIntegrationTests.java index 53090071a69..91241d4a65a 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingViewResolutionIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingViewResolutionIntegrationTests.java @@ -35,7 +35,7 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.reactive.config.ViewResolverRegistry; -import org.springframework.web.reactive.config.WebReactiveConfiguration; +import org.springframework.web.reactive.config.WebReactiveConfigurationSupport; import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer; import org.springframework.web.server.ServerWebExchange; @@ -81,7 +81,7 @@ public class RequestMappingViewResolutionIntegrationTests extends AbstractReques @Configuration @ComponentScan(resourcePattern = "**/RequestMappingViewResolutionIntegrationTests$*.class") @SuppressWarnings({"unused", "WeakerAccess"}) - static class WebConfig extends WebReactiveConfiguration { + static class WebConfig extends WebReactiveConfigurationSupport { @Override protected void configureViewResolvers(ViewResolverRegistry registry) { diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java index 40f4552ffd4..fa77b4d2973 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java @@ -36,7 +36,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.reactive.WebClient; import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.reactive.config.WebReactiveConfiguration; +import org.springframework.web.reactive.config.EnableWebReactive; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import static org.springframework.web.client.reactive.ClientWebRequestBuilders.get; @@ -163,8 +163,9 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests { } @Configuration + @EnableWebReactive @SuppressWarnings("unused") - static class TestConfiguration extends WebReactiveConfiguration { + static class TestConfiguration { @Bean public SseController sseController() { From 7f6eaef3057a200fbd93e82dc5144bd866c0772e Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 14 Oct 2016 17:55:40 -0400 Subject: [PATCH 2/4] Improve WebReactiveConfigurerComposite --- .../WebReactiveConfigurerComposite.java | 78 +++++++------------ 1 file changed, 30 insertions(+), 48 deletions(-) diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java index 0e5979e98a5..ec83ac67781 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java @@ -3,6 +3,8 @@ package org.springframework.web.reactive.config; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; import org.springframework.format.FormatterRegistry; import org.springframework.http.codec.HttpMessageReader; @@ -19,32 +21,25 @@ import org.springframework.web.reactive.result.method.annotation.RequestMappingH * A {@link WebReactiveConfigurer} that delegates to one or more others. * * @author Brian Clozel + * @author Rossen Stoyanchev * @since 5.0 */ public class WebReactiveConfigurerComposite implements WebReactiveConfigurer { private final List delegates = new ArrayList<>(); + public void addWebReactiveConfigurers(List configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.delegates.addAll(configurers); } } + @Override public Optional createRequestMappingHandlerMapping() { - Optional selected = Optional.empty(); - for (WebReactiveConfigurer configurer : this.delegates) { - Optional handlerMapping = configurer.createRequestMappingHandlerMapping(); - if (handlerMapping.isPresent()) { - if (selected != null) { - throw new IllegalStateException("No unique RequestMappingHandlerMapping found: {" + - selected.get() + ", " + handlerMapping.get() + "}"); - } - selected = handlerMapping; - } - } - return selected; + return createSingleBean(WebReactiveConfigurer::createRequestMappingHandlerMapping, + RequestMappingHandlerMapping.class); } @Override @@ -69,18 +64,8 @@ public class WebReactiveConfigurerComposite implements WebReactiveConfigurer { @Override public Optional createRequestMappingHandlerAdapter() { - Optional selected = Optional.empty(); - for (WebReactiveConfigurer configurer : this.delegates) { - Optional handlerAdapter = configurer.createRequestMappingHandlerAdapter(); - if (handlerAdapter.isPresent()) { - if (selected != null) { - throw new IllegalStateException("No unique RequestMappingHandlerAdapter found: {" + - selected.get() + ", " + handlerAdapter.get() + "}"); - } - selected = handlerAdapter; - } - } - return selected; + return createSingleBean(WebReactiveConfigurer::createRequestMappingHandlerAdapter, + RequestMappingHandlerAdapter.class); } @Override @@ -105,34 +90,12 @@ public class WebReactiveConfigurerComposite implements WebReactiveConfigurer { @Override public Optional getValidator() { - Optional selected = Optional.empty(); - for (WebReactiveConfigurer configurer : this.delegates) { - Optional validator = configurer.getValidator(); - if (validator.isPresent()) { - if (selected != null) { - throw new IllegalStateException("No unique Validator found: {" + - selected.get() + ", " + validator.get() + "}"); - } - selected = validator; - } - } - return selected; + return createSingleBean(WebReactiveConfigurer::getValidator, Validator.class); } @Override public Optional getMessageCodesResolver() { - Optional selected = Optional.empty(); - for (WebReactiveConfigurer configurer : this.delegates) { - Optional messageCodesResolver = configurer.getMessageCodesResolver(); - if (messageCodesResolver.isPresent()) { - if (selected != null) { - throw new IllegalStateException("No unique MessageCodesResolver found: {" + - selected.get() + ", " + messageCodesResolver.get() + "}"); - } - selected = messageCodesResolver; - } - } - return selected; + return createSingleBean(WebReactiveConfigurer::getMessageCodesResolver, MessageCodesResolver.class); } @Override @@ -149,4 +112,23 @@ public class WebReactiveConfigurerComposite implements WebReactiveConfigurer { public void configureViewResolvers(ViewResolverRegistry registry) { this.delegates.stream().forEach(delegate -> delegate.configureViewResolvers(registry)); } + + private Optional createSingleBean(Function> factory, + Class beanType) { + + List> result = this.delegates.stream() + .map(factory).filter(Optional::isPresent).collect(Collectors.toList()); + + if (result.isEmpty()) { + return Optional.empty(); + } + else if (result.size() == 1) { + return result.get(1); + } + else { + throw new IllegalStateException("More than one WebReactiveConfigurer implements " + + beanType.getSimpleName() + " factory method."); + } + } + } From 44717af0e593dac03a6bbdaa8d904935560ce85d Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 14 Oct 2016 18:02:00 -0400 Subject: [PATCH 3/4] Update order of method in WebReactiveConfigurer --- .../config/WebReactiveConfigurer.java | 50 ++++++++++--------- .../WebReactiveConfigurerComposite.java | 40 +++++++-------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java index 645e6d0bb30..fd203c04fc5 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java @@ -31,15 +31,6 @@ import org.springframework.web.reactive.result.method.annotation.RequestMappingH */ public interface WebReactiveConfigurer { - /** - * Provide a custom sub-class of {@link RequestMappingHandlerMapping} - * instead of the one created by default. - * The default implementation returns {@code Optional.empty()}. - */ - default Optional createRequestMappingHandlerMapping() { - return Optional.empty(); - } - /** * Configure how the requested content type is resolved. *

The given builder will create a composite of multiple @@ -73,15 +64,6 @@ public interface WebReactiveConfigurer { default void addResourceHandlers(ResourceHandlerRegistry registry) { } - /** - * Provide a custom sub-class of {@link RequestMappingHandlerAdapter} - * instead of the one created by default. - * The default implementation returns {@code Optional.empty()}. - */ - default Optional createRequestMappingHandlerAdapter() { - return Optional.empty(); - } - /** * Provide custom argument resolvers without overriding the built-in ones. * @param resolvers a list of resolvers to add to the built-in ones @@ -93,16 +75,16 @@ public interface WebReactiveConfigurer { * Configure the message readers to use for decoding controller method arguments. *

If no message readers are specified, default readers will be added via * {@link WebReactiveConfigurationSupport#addDefaultHttpMessageReaders}. - * @param messageReaders a list to add message readers to, initially an empty list + * @param readers a list to add message readers to, initially an empty list */ - default void configureMessageReaders(List> messageReaders) { + default void configureMessageReaders(List> readers) { } /** * Modify the list of message readers to use for decoding controller method arguments, * for example to add some in addition to the ones already configured. */ - default void extendMessageReaders(List> messageReaders) { + default void extendMessageReaders(List> readers) { } /** @@ -134,16 +116,16 @@ public interface WebReactiveConfigurer { * Configure the message writers to use for encoding return values. *

If no message writers are specified, default writers will be added via * {@link WebReactiveConfigurationSupport#addDefaultHttpMessageWriters(List)}. - * @param messageWriters a list to add message writers to, initially an empty list + * @param writers a list to add message writers to, initially an empty list */ - default void configureMessageWriters(List> messageWriters) { + default void configureMessageWriters(List> writers) { } /** * Modify the list of message writers to use for encoding return values, * for example to add some in addition to the ones already configured. */ - default void extendMessageWriters(List> messageWriters) { + default void extendMessageWriters(List> writers) { } /** @@ -153,4 +135,24 @@ public interface WebReactiveConfigurer { default void configureViewResolvers(ViewResolverRegistry registry) { } + /** + * Factory method for the {@link RequestMappingHandlerMapping} bean creating + * an instance or a custom extension of it. Note that only one configurer + * is allowed to implement this method. + * The default implementation returns {@code Optional.empty()}. + */ + default Optional createRequestMappingHandlerMapping() { + return Optional.empty(); + } + + /** + * Factory method for the {@link RequestMappingHandlerAdapter} bean creating + * an instance or a custom extension of it. Note that only one configurer + * is allowed to implement this method. + * The default implementation returns {@code Optional.empty()}. + */ + default Optional createRequestMappingHandlerAdapter() { + return Optional.empty(); + } + } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java index ec83ac67781..00fce5558d8 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java @@ -36,12 +36,6 @@ public class WebReactiveConfigurerComposite implements WebReactiveConfigurer { } - @Override - public Optional createRequestMappingHandlerMapping() { - return createSingleBean(WebReactiveConfigurer::createRequestMappingHandlerMapping, - RequestMappingHandlerMapping.class); - } - @Override public void configureRequestedContentTypeResolver(RequestedContentTypeResolverBuilder builder) { this.delegates.stream().forEach(delegate -> delegate.configureRequestedContentTypeResolver(builder)); @@ -62,25 +56,19 @@ public class WebReactiveConfigurerComposite implements WebReactiveConfigurer { this.delegates.stream().forEach(delegate -> delegate.addResourceHandlers(registry)); } - @Override - public Optional createRequestMappingHandlerAdapter() { - return createSingleBean(WebReactiveConfigurer::createRequestMappingHandlerAdapter, - RequestMappingHandlerAdapter.class); - } - @Override public void addArgumentResolvers(List resolvers) { this.delegates.stream().forEach(delegate -> delegate.addArgumentResolvers(resolvers)); } @Override - public void configureMessageReaders(List> messageReaders) { - this.delegates.stream().forEach(delegate -> delegate.configureMessageReaders(messageReaders)); + public void configureMessageReaders(List> readers) { + this.delegates.stream().forEach(delegate -> delegate.configureMessageReaders(readers)); } @Override - public void extendMessageReaders(List> messageReaders) { - this.delegates.stream().forEach(delegate -> delegate.extendMessageReaders(messageReaders)); + public void extendMessageReaders(List> readers) { + this.delegates.stream().forEach(delegate -> delegate.extendMessageReaders(readers)); } @Override @@ -99,13 +87,13 @@ public class WebReactiveConfigurerComposite implements WebReactiveConfigurer { } @Override - public void configureMessageWriters(List> messageWriters) { - this.delegates.stream().forEach(delegate -> delegate.configureMessageWriters(messageWriters)); + public void configureMessageWriters(List> writers) { + this.delegates.stream().forEach(delegate -> delegate.configureMessageWriters(writers)); } @Override - public void extendMessageWriters(List> messageWriters) { - this.delegates.stream().forEach(delegate -> delegate.extendMessageWriters(messageWriters)); + public void extendMessageWriters(List> writers) { + this.delegates.stream().forEach(delegate -> delegate.extendMessageWriters(writers)); } @Override @@ -113,6 +101,18 @@ public class WebReactiveConfigurerComposite implements WebReactiveConfigurer { this.delegates.stream().forEach(delegate -> delegate.configureViewResolvers(registry)); } + @Override + public Optional createRequestMappingHandlerMapping() { + return createSingleBean(WebReactiveConfigurer::createRequestMappingHandlerMapping, + RequestMappingHandlerMapping.class); + } + + @Override + public Optional createRequestMappingHandlerAdapter() { + return createSingleBean(WebReactiveConfigurer::createRequestMappingHandlerAdapter, + RequestMappingHandlerAdapter.class); + } + private Optional createSingleBean(Function> factory, Class beanType) { From c7fb851e5af1b3ab62e8421afb5eb84cb320102a Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 14 Oct 2016 18:11:16 -0400 Subject: [PATCH 4/4] Polish --- .../DelegatingWebReactiveConfiguration.java | 16 ++++++++ .../reactive/config/EnableWebReactive.java | 37 +++++++++---------- .../config/WebReactiveConfigurer.java | 16 ++++++++ .../WebReactiveConfigurerComposite.java | 16 ++++++++ ...legatingWebReactiveConfigurationTests.java | 16 ++++++++ src/asciidoc/web-reactive.adoc | 2 +- 6 files changed, 83 insertions(+), 20 deletions(-) diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfiguration.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfiguration.java index 4636f358c13..d92e6318c0b 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfiguration.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfiguration.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.web.reactive.config; import java.util.List; diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/EnableWebReactive.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/EnableWebReactive.java index 3a11ea5bf19..ed55479e151 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/EnableWebReactive.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/EnableWebReactive.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.web.reactive.config; import java.lang.annotation.Documented; @@ -47,25 +63,8 @@ import org.springframework.context.annotation.Import; *

If {@link WebReactiveConfigurer} does not expose some advanced setting that * needs to be configured, consider removing the {@code @EnableWebReactive} * annotation and extending directly from {@link WebReactiveConfigurationSupport} - * or {@link DelegatingWebReactiveConfiguration}, e.g.: - * - *

- * @Configuration
- * @ComponentScan(basePackageClasses = { MyConfiguration.class })
- * public class MyConfiguration extends WebReactiveConfigurationSupport {
- *
- * 	   @Override
- *	   public void addFormatters(FormatterRegistry formatterRegistry) {
- *         formatterRegistry.addConverter(new MyConverter());
- *	   }
- *
- *	   @Bean
- *	   public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
- *         // Create or delegate to "super" to create and
- *         // customize properties of RequestMappingHandlerAdapter
- *	   }
- * }
- * 
+ * or {@link DelegatingWebReactiveConfiguration} if you still want to allow + * {@link WebReactiveConfigurer} instances to customize the configuration. * * @author Brian Clozel * @since 5.0 diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java index fd203c04fc5..7dc34095c1c 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurer.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.web.reactive.config; import java.util.List; diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java index 00fce5558d8..8f200f3a8d3 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfigurerComposite.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.web.reactive.config; import java.util.ArrayList; diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfigurationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfigurationTests.java index 49c2c32e4ce..05cad3d87ff 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfigurationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/config/DelegatingWebReactiveConfigurationTests.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.web.reactive.config; import java.util.Collections; diff --git a/src/asciidoc/web-reactive.adoc b/src/asciidoc/web-reactive.adoc index dc143fe3ec3..1d0e998658b 100644 --- a/src/asciidoc/web-reactive.adoc +++ b/src/asciidoc/web-reactive.adoc @@ -182,7 +182,7 @@ For the bootstrap code start with: [source,java,indent=0] [subs="verbatim,quotes"] ---- -ApplicationContext context = new AnnotationConfigApplicationContext(WebReactiveConfiguration.class); // (1) +ApplicationContext context = new AnnotationConfigApplicationContext(DelegatingWebReactiveConfiguration.class); // (1) HttpHandler handler = DispatcherHandler.toHttpHandler(context); // (2) ----