diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java index 43f2e812f9..4204f08077 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java @@ -70,6 +70,13 @@ import org.springframework.web.server.handler.ResponseStatusExceptionHandler; */ public class WebFluxConfigurationSupport implements ApplicationContextAware { + static final boolean jackson2Present = + ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", + WebFluxConfigurationSupport.class.getClassLoader()) && + ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", + WebFluxConfigurationSupport.class.getClassLoader()); + + private Map corsConfigurations; private PathMatchConfigurer pathMatchConfigurer; @@ -148,7 +155,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { */ protected Map getDefaultMediaTypeMappings() { Map map = new HashMap<>(); - if (ServerCodecConfigurer.jackson2Present) { + if (jackson2Present) { map.put("json", MediaType.APPLICATION_JSON); } return map; @@ -266,7 +273,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { */ protected final ServerCodecConfigurer getMessageCodecsConfigurer() { if (this.messageCodecsConfigurer == null) { - this.messageCodecsConfigurer = new ServerCodecConfigurer(); + this.messageCodecsConfigurer = ServerCodecConfigurer.create(); configureHttpMessageCodecs(this.getMessageCodecsConfigurer()); } return this.messageCodecsConfigurer; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java index de773b4bda..4ecdd878be 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java @@ -19,15 +19,11 @@ package org.springframework.web.reactive.function.client; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Stream; -import org.springframework.context.ApplicationContext; -import org.springframework.core.codec.Decoder; -import org.springframework.core.codec.Encoder; import org.springframework.http.codec.ClientCodecConfigurer; -import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.util.Assert; @@ -40,51 +36,37 @@ import org.springframework.util.Assert; */ class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Builder { - private final List> messageReaders = new ArrayList<>(); + private final ClientCodecConfigurer codecConfigurer = ClientCodecConfigurer.create(); - private final List> messageWriters = new ArrayList<>(); + public DefaultExchangeStrategiesBuilder() { + this.codecConfigurer.registerDefaults(false); + } public void defaultConfiguration() { - ClientCodecConfigurer configurer = new ClientCodecConfigurer(); - configurer.getReaders().forEach(this::messageReader); - configurer.getWriters().forEach(this::messageWriter); - } - - public void applicationContext(ApplicationContext applicationContext) { - applicationContext.getBeansOfType(HttpMessageReader.class).values().forEach(this::messageReader); - applicationContext.getBeansOfType(HttpMessageWriter.class).values().forEach(this::messageWriter); + this.codecConfigurer.registerDefaults(true); } @Override - public ExchangeStrategies.Builder messageReader(HttpMessageReader messageReader) { - Assert.notNull(messageReader, "'messageReader' must not be null"); - this.messageReaders.add(messageReader); + public ExchangeStrategies.Builder defaultCodecs( + Consumer consumer) { + Assert.notNull(consumer, "'consumer' must not be null"); + consumer.accept(this.codecConfigurer.defaultCodecs()); return this; } @Override - public ExchangeStrategies.Builder decoder(Decoder decoder) { - Assert.notNull(decoder, "'decoder' must not be null"); - return messageReader(new DecoderHttpMessageReader<>(decoder)); - } - - @Override - public ExchangeStrategies.Builder messageWriter(HttpMessageWriter messageWriter) { - Assert.notNull(messageWriter, "'messageWriter' must not be null"); - this.messageWriters.add(messageWriter); + public ExchangeStrategies.Builder customCodecs( + Consumer consumer) { + Assert.notNull(consumer, "'consumer' must not be null"); + consumer.accept(this.codecConfigurer.customCodecs()); return this; } - @Override - public ExchangeStrategies.Builder encoder(Encoder encoder) { - Assert.notNull(encoder, "'encoder' must not be null"); - return messageWriter(new EncoderHttpMessageWriter<>(encoder)); - } - @Override public ExchangeStrategies build() { - return new DefaultExchangeStrategies(this.messageReaders, this.messageWriters); + return new DefaultExchangeStrategies(this.codecConfigurer.getReaders(), + this.codecConfigurer.getWriters()); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java index b4c092103a..6aee224e7a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java @@ -16,15 +16,13 @@ package org.springframework.web.reactive.function.client; +import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Stream; -import org.springframework.context.ApplicationContext; -import org.springframework.core.codec.Decoder; -import org.springframework.core.codec.Encoder; +import org.springframework.http.codec.ClientCodecConfigurer; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; -import org.springframework.util.Assert; /** * Defines the strategies for invoking {@link ExchangeFunction}s. An instance of @@ -66,48 +64,6 @@ public interface ExchangeStrategies { return builder().build(); } - /** - * Return a new {@code ExchangeStrategies} based on the given - * {@linkplain ApplicationContext application context}. - * The returned supplier will search for all {@link HttpMessageReader}, and - * {@link HttpMessageWriter} instances in the given application context and return - * them for {@link #messageReaders()}, and {@link #messageWriters()} respectively. - * @param applicationContext the application context to base the strategies on - * @return the new {@code ExchangeStrategies} - */ - static ExchangeStrategies of(ApplicationContext applicationContext) { - return builder(applicationContext).build(); - } - - /** - * Return a new {@code ExchangeStrategies} described by the given supplier functions. - * All provided supplier function parameters can be {@code null} to indicate an empty - * stream is to be returned. - * @param messageReaders the supplier function for {@link HttpMessageReader} instances - * (can be {@code null}) - * @param messageWriters the supplier function for {@link HttpMessageWriter} instances - * (can be {@code null}) - * @return the new {@code ExchangeStrategies} - */ - static ExchangeStrategies of(Supplier>> messageReaders, - Supplier>> messageWriters) { - - return new ExchangeStrategies() { - @Override - public Supplier>> messageReaders() { - return checkForNull(messageReaders); - } - @Override - public Supplier>> messageWriters() { - return checkForNull(messageWriters); - } - private Supplier> checkForNull(Supplier> supplier) { - return supplier != null ? supplier : Stream::empty; - } - }; - } - - // Builder methods /** @@ -120,21 +76,6 @@ public interface ExchangeStrategies { return builder; } - /** - * Return a mutable builder based on the given {@linkplain ApplicationContext application context}. - * The returned builder will search for all {@link HttpMessageReader}, and - * {@link HttpMessageWriter} instances in the given application context and return them for - * {@link #messageReaders()}, and {@link #messageWriters()}. - * @param applicationContext the application context to base the strategies on - * @return the builder - */ - static Builder builder(ApplicationContext applicationContext) { - Assert.notNull(applicationContext, "ApplicationContext must not be null"); - DefaultExchangeStrategiesBuilder builder = new DefaultExchangeStrategiesBuilder(); - builder.applicationContext(applicationContext); - return builder; - } - /** * Return a mutable, empty builder for a {@code ExchangeStrategies}. * @return the builder @@ -150,34 +91,20 @@ public interface ExchangeStrategies { interface Builder { /** - * Add the given message reader to this builder. - * @param messageReader the message reader to add + * Customize the list of default client-side HTTP message readers and writers. + * @param consumer the consumer to customize the default codecs * @return this builder + * @see #customCodecs(Consumer) */ - Builder messageReader(HttpMessageReader messageReader); + Builder defaultCodecs(Consumer consumer); /** - * Add the given decoder to this builder. This is a convenient alternative to adding a - * {@link org.springframework.http.codec.DecoderHttpMessageReader} that wraps the given decoder. - * @param decoder the decoder to add + * Customize the list of custom client-side HTTP message readers and writers. + * @param consumer the consumer to customize the custom codecs * @return this builder + * @see #defaultCodecs(Consumer) */ - Builder decoder(Decoder decoder); - - /** - * Add the given message writer to this builder. - * @param messageWriter the message writer to add - * @return this builder - */ - Builder messageWriter(HttpMessageWriter messageWriter); - - /** - * Add the given encoder to this builder. This is a convenient alternative to adding a - * {@link org.springframework.http.codec.EncoderHttpMessageWriter} that wraps the given encoder. - * @param encoder the encoder to add - * @return this builder - */ - Builder encoder(Encoder encoder); + Builder customCodecs(Consumer consumer); /** * Builds the {@link ExchangeStrategies}. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java index e93783225a..2ff119d80a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java @@ -21,17 +21,15 @@ import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; -import org.springframework.core.codec.Decoder; -import org.springframework.core.codec.Encoder; +import org.springframework.http.codec.CodecConfigurer; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ServerCodecConfigurer; -import org.springframework.http.codec.json.Jackson2JsonDecoder; -import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.util.Assert; import org.springframework.web.reactive.result.view.ViewResolver; @@ -49,7 +47,8 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { .map(Locale::forLanguageTag).findFirst(); - private final ServerCodecConfigurer codecConfigurer = new ServerCodecConfigurer(); + private final ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create(); + private final List viewResolvers = new ArrayList<>(); private Function> localeResolver; @@ -66,51 +65,18 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { } @Override - public HandlerStrategies.Builder serverSentEventEncoder(Encoder encoder) { - Assert.notNull(encoder, "'encoder' must not be null"); - this.codecConfigurer.defaultCodecs().serverSentEventEncoder(encoder); + public HandlerStrategies.Builder defaultCodecs( + Consumer consumer) { + Assert.notNull(consumer, "'consumer' must not be null"); + consumer.accept(this.codecConfigurer.defaultCodecs()); return this; } @Override - public HandlerStrategies.Builder jackson2Decoder(Jackson2JsonDecoder decoder) { - Assert.notNull(decoder, "'decoder' must not be null"); - this.codecConfigurer.defaultCodecs().jackson2Decoder(decoder); - return this; - } - - @Override - public HandlerStrategies.Builder jackson2Encoder(Jackson2JsonEncoder encoder) { - Assert.notNull(encoder, "'encoder' must not be null"); - this.codecConfigurer.defaultCodecs().jackson2Encoder(encoder); - return this; - } - - @Override - public HandlerStrategies.Builder customDecoder(Decoder decoder) { - Assert.notNull(decoder, "'decoder' must not be null"); - this.codecConfigurer.customCodecs().decoder(decoder); - return this; - } - - @Override - public HandlerStrategies.Builder customEncoder(Encoder encoder) { - Assert.notNull(encoder, "'encoder' must not be null"); - this.codecConfigurer.customCodecs().encoder(encoder); - return this; - } - - @Override - public HandlerStrategies.Builder customMessageReader(HttpMessageReader reader) { - Assert.notNull(reader, "'reader' must not be null"); - this.codecConfigurer.customCodecs().reader(reader); - return this; - } - - @Override - public HandlerStrategies.Builder customMessageWriter(HttpMessageWriter writer) { - Assert.notNull(writer, "'writer' must not be null"); - this.codecConfigurer.customCodecs().writer(writer); + public HandlerStrategies.Builder customCodecs( + Consumer consumer) { + Assert.notNull(consumer, "'consumer' must not be null"); + consumer.accept(this.codecConfigurer.customCodecs()); return this; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/HandlerStrategies.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/HandlerStrategies.java index 16391c69ae..14e37996ad 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/HandlerStrategies.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/HandlerStrategies.java @@ -18,18 +18,14 @@ package org.springframework.web.reactive.function.server; import java.util.Locale; import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; -import org.springframework.core.codec.Decoder; -import org.springframework.core.codec.Encoder; -import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; -import org.springframework.http.codec.json.Jackson2JsonDecoder; -import org.springframework.http.codec.json.Jackson2JsonEncoder; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; /** @@ -113,60 +109,20 @@ public interface HandlerStrategies { interface Builder { /** - * Configure the {@code Encoder} to use for Server-Sent Events. - *

By default the {@link #jackson2Encoder} override is used for SSE. - * @param encoder the encoder to use + * Customize the list of default server-side HTTP message readers and writers. + * @param consumer the consumer to customize the default codecs * @return this builder + * @see #customCodecs(Consumer) */ - Builder serverSentEventEncoder(Encoder encoder); + Builder defaultCodecs(Consumer consumer); /** - * Override the default Jackson {@code Decoder}. - * @param decoder the decoder to use + * Customize the list of custom server-side HTTP message readers and writers. + * @param consumer the consumer to customize the custom codecs * @return this builder + * @see #defaultCodecs(Consumer) */ - Builder jackson2Decoder(Jackson2JsonDecoder decoder); - - /** - * Override the default Jackson {@code Encoder} for JSON. - * @param encoder the encoder to use - * @return this builder - */ - Builder jackson2Encoder(Jackson2JsonEncoder encoder); - - /** - * Add a custom {@code Decoder} internally wrapped with - * {@link DecoderHttpMessageReader}). - * @param decoder the decoder to add - * @return this builder - */ - Builder customDecoder(Decoder decoder); - - /** - * Add a custom {@code Encoder}, internally wrapped with - * {@link EncoderHttpMessageWriter}. - * @param encoder the encoder to add - * @return this builder - */ - Builder customEncoder(Encoder encoder); - - /** - * Add a custom {@link HttpMessageReader}. For readers of type - * {@link DecoderHttpMessageReader} consider using the shortcut - * {@link #customDecoder(Decoder)} instead. - * @param reader the reader to add - * @return this builder - */ - Builder customMessageReader(HttpMessageReader reader); - - /** - * Add a custom {@link HttpMessageWriter}. For readers of type - * {@link EncoderHttpMessageWriter} consider using the shortcut - * {@link #customEncoder(Encoder)} instead. - * @param writer the writer to add - * @return this builder - */ - Builder customMessageWriter(HttpMessageWriter writer); + Builder customCodecs(Consumer consumer); /** * Add the given view resolver to this builder. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java index 5bc91d9356..844a985a6a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java @@ -146,7 +146,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application public void afterPropertiesSet() throws Exception { if (this.messageCodecConfigurer == null) { - this.messageCodecConfigurer = new ServerCodecConfigurer(); + this.messageCodecConfigurer = ServerCodecConfigurer.create(); } if (this.argumentResolverConfigurer == null) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java index c54387fe82..d98560488f 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java @@ -16,25 +16,9 @@ package org.springframework.web.reactive.function.client; -import java.util.Collections; -import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.junit.Test; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.core.ResolvableType; -import org.springframework.http.MediaType; -import org.springframework.http.ReactiveHttpInputMessage; -import org.springframework.http.ReactiveHttpOutputMessage; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.http.codec.HttpMessageWriter; import static org.junit.Assert.*; @@ -51,82 +35,10 @@ public class ExchangeStrategiesTests { } @Test - public void ofSuppliers() { - HttpMessageReader messageReader = new DummyMessageReader(); - HttpMessageWriter messageWriter = new DummyMessageWriter(); - - ExchangeStrategies strategies = ExchangeStrategies.of( - () -> Stream.of(messageReader), - () -> Stream.of(messageWriter)); - - assertEquals(1L, strategies.messageReaders().get().collect(Collectors.counting()).longValue()); - assertEquals(Optional.of(messageReader), strategies.messageReaders().get().findFirst()); - - assertEquals(1L, strategies.messageWriters().get().collect(Collectors.counting()).longValue()); - assertEquals(Optional.of(messageWriter), strategies.messageWriters().get().findFirst()); - } - - @Test - public void toConfiguration() throws Exception { - StaticApplicationContext applicationContext = new StaticApplicationContext(); - applicationContext.registerSingleton("messageWriter", DummyMessageWriter.class); - applicationContext.registerSingleton("messageReader", DummyMessageReader.class); - applicationContext.refresh(); - - ExchangeStrategies strategies = ExchangeStrategies.of(applicationContext); - assertTrue(strategies.messageReaders().get() - .allMatch(r -> r instanceof DummyMessageReader)); - assertTrue(strategies.messageWriters().get() - .allMatch(r -> r instanceof DummyMessageWriter)); - - } - - - private static class DummyMessageWriter implements HttpMessageWriter { - - @Override - public boolean canWrite(ResolvableType type, MediaType mediaType) { - return false; - } - - @Override - public List getWritableMediaTypes() { - return Collections.emptyList(); - } - - @Override - public Mono write(Publisher inputStream, ResolvableType type, - MediaType contentType, - ReactiveHttpOutputMessage message, - Map hints) { - return Mono.empty(); - } - } - - - private static class DummyMessageReader implements HttpMessageReader { - - @Override - public boolean canRead(ResolvableType type, MediaType mediaType) { - return false; - } - - @Override - public List getReadableMediaTypes() { - return Collections.emptyList(); - } - - @Override - public Flux read(ResolvableType type, ReactiveHttpInputMessage message, - Map hints) { - return Flux.empty(); - } - - @Override - public Mono readMono(ResolvableType type, ReactiveHttpInputMessage message, - Map hints) { - return Mono.empty(); - } + public void withDefaults() { + ExchangeStrategies strategies = ExchangeStrategies.withDefaults(); + assertNotEquals(Optional.empty(), strategies.messageReaders().get().findFirst()); + assertNotEquals(Optional.empty(), strategies.messageWriters().get().findFirst()); } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilderTests.java index 38484cf6df..9556d7aa5f 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilderTests.java @@ -191,7 +191,7 @@ public class DefaultEntityResponseBuilderTests { MockServerWebExchange exchange = MockServerHttpRequest.get("http://localhost").toExchange(); HandlerStrategies strategies = HandlerStrategies.empty() - .customMessageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes())) + .customCodecs(configurer -> configurer.writer(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()))) .build(); StepVerifier.create(result) diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java index e77bf5dec3..59dda9bc24 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java @@ -67,7 +67,7 @@ public class ControllerMethodResolverTests { resolvers.addCustomResolver(new CustomArgumentResolver()); resolvers.addCustomResolver(new CustomSyncArgumentResolver()); - ServerCodecConfigurer codecs = new ServerCodecConfigurer(); + ServerCodecConfigurer codecs = ServerCodecConfigurer.create(); codecs.customCodecs().decoder(new ByteArrayDecoder()); codecs.customCodecs().decoder(new ByteBufferDecoder());