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 2d2d3341490..e93783225a4 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 @@ -25,10 +25,13 @@ import java.util.function.Function; 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.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; @@ -46,40 +49,68 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { .map(Locale::forLanguageTag).findFirst(); - private final List> messageReaders = new ArrayList<>(); - - private final List> messageWriters = new ArrayList<>(); - + private final ServerCodecConfigurer codecConfigurer = new ServerCodecConfigurer(); private final List viewResolvers = new ArrayList<>(); private Function> localeResolver; - public void defaultConfiguration() { - ServerCodecConfigurer configurer = new ServerCodecConfigurer(); - configurer.getReaders().forEach(this::messageReader); - configurer.getWriters().forEach(this::messageWriter); - localeResolver(DEFAULT_LOCALE_RESOLVER); + + public DefaultHandlerStrategiesBuilder() { + this.codecConfigurer.registerDefaults(false); } - public void applicationContext(ApplicationContext applicationContext) { - applicationContext.getBeansOfType(HttpMessageReader.class).values().forEach(this::messageReader); - applicationContext.getBeansOfType(HttpMessageWriter.class).values().forEach(this::messageWriter); - applicationContext.getBeansOfType(ViewResolver.class).values().forEach(this::viewResolver); + public void defaultConfiguration() { + this.codecConfigurer.registerDefaults(true); localeResolver(DEFAULT_LOCALE_RESOLVER); } @Override - public HandlerStrategies.Builder messageReader(HttpMessageReader messageReader) { - Assert.notNull(messageReader, "'messageReader' must not be null"); - this.messageReaders.add(messageReader); + public HandlerStrategies.Builder serverSentEventEncoder(Encoder encoder) { + Assert.notNull(encoder, "'encoder' must not be null"); + this.codecConfigurer.defaultCodecs().serverSentEventEncoder(encoder); return this; } @Override - public HandlerStrategies.Builder messageWriter(HttpMessageWriter messageWriter) { - Assert.notNull(messageWriter, "'messageWriter' must not be null"); - this.messageWriters.add(messageWriter); + 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); return this; } @@ -99,8 +130,8 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { @Override public HandlerStrategies build() { - return new DefaultHandlerStrategies(this.messageReaders, this.messageWriters, - this.viewResolvers, localeResolver); + return new DefaultHandlerStrategies(this.codecConfigurer.getReaders(), + this.codecConfigurer.getWriters(), this.viewResolvers, this.localeResolver); } @@ -146,8 +177,8 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { } @Override - public Function> localeResolver() { - return this.localeResolver; + public Supplier>> localeResolver() { + return () -> this.localeResolver; } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java index ec4c8747d57..6b12f797863 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java @@ -179,7 +179,7 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder { .orElseThrow(() -> new IllegalStateException( "Could not find ServerRequest in exchange attributes")); - return strategies.localeResolver() + return strategies.localeResolver().get() .apply(request) .orElse(Locale.getDefault()); 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 641b144ec28..16391c69ae1 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 @@ -22,10 +22,14 @@ import java.util.function.Function; 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.DecoderHttpMessageReader; +import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; -import org.springframework.util.Assert; +import org.springframework.http.codec.json.Jackson2JsonDecoder; +import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.web.reactive.result.view.ViewResolver; /** @@ -69,7 +73,7 @@ public interface HandlerStrategies { * Supply a function that resolves the locale of a given {@link ServerRequest}. * @return the locale resolver */ - Function> localeResolver(); + Supplier>> localeResolver(); // Static methods @@ -82,20 +86,6 @@ public interface HandlerStrategies { return builder().build(); } - /** - * Return a new {@code HandlerStrategies} based on the given - * {@linkplain ApplicationContext application context}. - * The returned supplier will search for all {@link HttpMessageReader}, {@link HttpMessageWriter}, - * and {@link ViewResolver} instances in the given application context and return them for - * {@link #messageReaders()}, {@link #messageWriters()}, and {@link #viewResolvers()} - * respectively. - * @param applicationContext the application context to base the strategies on - * @return the new {@code HandlerStrategies} - */ - static HandlerStrategies of(ApplicationContext applicationContext) { - return builder(applicationContext).build(); - } - // Builder methods /** @@ -108,22 +98,6 @@ public interface HandlerStrategies { return builder; } - /** - * Return a mutable builder based on the given {@linkplain ApplicationContext application context}. - * The returned builder will search for all {@link HttpMessageReader}, {@link HttpMessageWriter}, - * and {@link ViewResolver} instances in the given application context and return them for - * {@link #messageReaders()}, {@link #messageWriters()}, and {@link #viewResolvers()} - * respectively. - * @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"); - DefaultHandlerStrategiesBuilder builder = new DefaultHandlerStrategiesBuilder(); - builder.applicationContext(applicationContext); - return builder; - } - /** * Return a mutable, empty builder for a {@code HandlerStrategies}. * @return the builder @@ -139,18 +113,60 @@ public interface HandlerStrategies { interface Builder { /** - * Add the given message reader to this builder. - * @param messageReader the message reader to add + * 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 * @return this builder */ - Builder messageReader(HttpMessageReader messageReader); + Builder serverSentEventEncoder(Encoder encoder); /** - * Add the given message writer to this builder. - * @param messageWriter the message writer to add + * Override the default Jackson {@code Decoder}. + * @param decoder the decoder to use * @return this builder */ - Builder messageWriter(HttpMessageWriter messageWriter); + 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); /** * Add the given view resolver to this builder. 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 1474d830d40..38484cf6dfc 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 @@ -46,8 +46,7 @@ import org.springframework.mock.http.server.reactive.test.MockServerWebExchange; import org.springframework.web.reactive.function.BodyInserter; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; +import static org.junit.Assert.*; /** * @author Arjen Poutsma @@ -192,7 +191,7 @@ public class DefaultEntityResponseBuilderTests { MockServerWebExchange exchange = MockServerHttpRequest.get("http://localhost").toExchange(); HandlerStrategies strategies = HandlerStrategies.empty() - .messageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes())) + .customMessageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes())) .build(); StepVerifier.create(result) diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java index 7563397ad80..4948887f7a2 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java @@ -49,7 +49,7 @@ import org.springframework.web.reactive.function.server.support.ServerResponseRe import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import static org.springframework.web.reactive.function.BodyInserters.fromObject; import static org.springframework.web.reactive.function.BodyInserters.fromPublisher; import static org.springframework.web.reactive.function.server.RouterFunctions.route; @@ -141,8 +141,8 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr } @Override - public Function> localeResolver() { - return DefaultHandlerStrategiesBuilder.DEFAULT_LOCALE_RESOLVER; + public Supplier>> localeResolver() { + return () -> DefaultHandlerStrategiesBuilder.DEFAULT_LOCALE_RESOLVER; } }); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/HandlerStrategiesTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/HandlerStrategiesTests.java index 8a32fc29116..b2010c1d88e 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/HandlerStrategiesTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/HandlerStrategiesTests.java @@ -16,23 +16,9 @@ package org.springframework.web.reactive.function.server; -import java.util.Collections; -import java.util.List; -import java.util.Map; import java.util.Optional; 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.*; @@ -47,88 +33,16 @@ public class HandlerStrategiesTests { assertEquals(Optional.empty(), strategies.messageReaders().get().findFirst()); assertEquals(Optional.empty(), strategies.messageWriters().get().findFirst()); assertEquals(Optional.empty(), strategies.viewResolvers().get().findFirst()); + assertNull(strategies.localeResolver().get()); } @Test - public void ofSuppliers() { - HttpMessageReader messageReader = new DummyMessageReader(); - HttpMessageWriter messageWriter = new DummyMessageWriter(); - - HandlerStrategies strategies = HandlerStrategies.empty() - .messageReader(messageReader) - .messageWriter(messageWriter) - .build(); - - assertEquals(1L, ((Long) strategies.messageReaders().get().count()).longValue()); - assertEquals(Optional.of(messageReader), strategies.messageReaders().get().findFirst()); - - assertEquals(1L, ((Long) strategies.messageWriters().get().count()).longValue()); - assertEquals(Optional.of(messageWriter), strategies.messageWriters().get().findFirst()); - + public void withDefaults() { + HandlerStrategies strategies = HandlerStrategies.withDefaults(); + assertNotEquals(Optional.empty(), strategies.messageReaders().get().findFirst()); + assertNotEquals(Optional.empty(), strategies.messageWriters().get().findFirst()); assertEquals(Optional.empty(), strategies.viewResolvers().get().findFirst()); - } - - @Test - public void toConfiguration() throws Exception { - StaticApplicationContext applicationContext = new StaticApplicationContext(); - applicationContext.registerSingleton("messageWriter", DummyMessageWriter.class); - applicationContext.registerSingleton("messageReader", DummyMessageReader.class); - applicationContext.refresh(); - - HandlerStrategies strategies = HandlerStrategies.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(); - } + assertNotNull(strategies.localeResolver().get()); } }