diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultControllerSpec.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultControllerSpec.java index 512826045b..ad2577b2af 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultControllerSpec.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultControllerSpec.java @@ -25,8 +25,8 @@ import java.util.function.Consumer; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.format.FormatterRegistry; -import org.springframework.http.codec.ServerHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.validation.Validator; @@ -85,14 +85,8 @@ class DefaultControllerSpec extends AbstractMockServerSpec>> consumer) { - this.configurer.readersConsumer = consumer; - return this; - } - - @Override - public DefaultControllerSpec messageWriters(Consumer>> consumer) { - this.configurer.writersConsumer = consumer; + public DefaultControllerSpec httpMessageCodecs(Consumer consumer) { + this.configurer.messageCodecsConsumer = consumer; return this; } @@ -145,9 +139,9 @@ class DefaultControllerSpec extends AbstractMockServerSpec pathMatchConsumer; - private Consumer>> readersConsumer; + private Consumer messageCodecsConsumer; - private Consumer>> writersConsumer; + private Consumer>> writersConsumer; private Consumer formattersConsumer; @@ -178,16 +172,9 @@ class DefaultControllerSpec extends AbstractMockServerSpec> readers) { - if (this.readersConsumer != null) { - this.readersConsumer.accept(readers); - } - } - - @Override - public void extendMessageWriters(List> writers) { - if (this.writersConsumer != null) { - this.writersConsumer.accept(writers); + public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { + if (this.messageCodecsConsumer != null) { + this.messageCodecsConsumer.accept(configurer); } } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index 73869bac55..8b1750ecc1 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -34,8 +34,7 @@ import org.springframework.format.FormatterRegistry; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.client.reactive.ClientHttpRequest; -import org.springframework.http.codec.ServerHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.util.MultiValueMap; import org.springframework.validation.Validator; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; @@ -244,16 +243,10 @@ public interface WebTestClient { ControllerSpec pathMatching(Consumer consumer); /** - * Modify or extend the list of built-in message readers. - * @see WebFluxConfigurer#configureMessageReaders + * Modify or extend the list of built-in message readers and writers. + * @see WebFluxConfigurer#configureHttpMessageCodecs */ - ControllerSpec messageReaders(Consumer>> readers); - - /** - * Modify or extend the list of built-in message writers. - * @see WebFluxConfigurer#configureMessageWriters - */ - ControllerSpec messageWriters(Consumer>> writers); + ControllerSpec httpMessageCodecs(Consumer configurer); /** * Register formatters and converters to use for type conversion. diff --git a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java index 266a6dd46d..159eb503af 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java @@ -36,7 +36,7 @@ import org.springframework.util.Assert; /** * {@code HttpMessageReader} that wraps and delegates to a {@link Decoder}. * - *

Also a {@code ServerHttpMessageReader} that pre-resolves decoding hints + *

Also a {@code HttpMessageReader} that pre-resolves decoding hints * from the extra information available on the server side such as the request * or controller method parameter annotations. * @@ -45,7 +45,7 @@ import org.springframework.util.Assert; * @author Rossen Stoyanchev * @since 5.0 */ -public class DecoderHttpMessageReader implements ServerHttpMessageReader { +public class DecoderHttpMessageReader implements HttpMessageReader { private final Decoder decoder; @@ -102,7 +102,7 @@ public class DecoderHttpMessageReader implements ServerHttpMessageReader { } - // ServerHttpMessageReader... + // Server-side only... @Override public Flux read(ResolvableType actualType, ResolvableType elementType, @@ -129,13 +129,13 @@ public class DecoderHttpMessageReader implements ServerHttpMessageReader { /** * Get additional hints for decoding for example based on the server request * or annotations from controller method parameters. By default, delegate to - * the decoder if it is an instance of {@link HttpDecoder}. + * the decoder if it is an instance of {@link HttpMessageDecoder}. */ protected Map getReadHints(ResolvableType streamType, ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response) { - if (this.decoder instanceof HttpDecoder) { - HttpDecoder httpDecoder = (HttpDecoder) this.decoder; + if (this.decoder instanceof HttpMessageDecoder) { + HttpMessageDecoder httpDecoder = (HttpMessageDecoder) this.decoder; return httpDecoder.getDecodeHints(streamType, elementType, request, response); } return Collections.emptyMap(); diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index c7397c475b..52258f4b1c 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -37,7 +37,7 @@ import org.springframework.util.Assert; /** * {@code HttpMessageWriter} that wraps and delegates to a {@link Encoder}. * - *

Also a {@code ServerHttpMessageWriter} that pre-resolves encoding hints + *

Also a {@code HttpMessageWriter} that pre-resolves encoding hints * from the extra information available on the server side such as the request * or controller method annotations. * @@ -46,7 +46,7 @@ import org.springframework.util.Assert; * @author Rossen Stoyanchev * @since 5.0 */ -public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { +public class EncoderHttpMessageWriter implements HttpMessageWriter { private final Encoder encoder; @@ -130,13 +130,13 @@ public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { } private boolean isStreamingMediaType(MediaType contentType) { - return this.encoder instanceof HttpEncoder && - ((HttpEncoder) this.encoder).getStreamingMediaTypes().stream() + return this.encoder instanceof HttpMessageEncoder && + ((HttpMessageEncoder) this.encoder).getStreamingMediaTypes().stream() .anyMatch(contentType::isCompatibleWith); } - // ServerHttpMessageWriter... + // Server side only... @Override public Mono write(Publisher inputStream, ResolvableType actualType, @@ -153,13 +153,13 @@ public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { /** * Get additional hints for encoding for example based on the server request * or annotations from controller method parameters. By default, delegate to - * the encoder if it is an instance of {@link HttpEncoder}. + * the encoder if it is an instance of {@link HttpMessageEncoder}. */ protected Map getWriteHints(ResolvableType streamType, ResolvableType elementType, MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) { - if (this.encoder instanceof HttpEncoder) { - HttpEncoder httpEncoder = (HttpEncoder) this.encoder; + if (this.encoder instanceof HttpMessageEncoder) { + HttpMessageEncoder httpEncoder = (HttpMessageEncoder) this.encoder; return httpEncoder.getEncodeHints(streamType, elementType, mediaType, request, response); } return Collections.emptyMap(); diff --git a/spring-web/src/main/java/org/springframework/http/codec/HttpDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageDecoder.java similarity index 94% rename from spring-web/src/main/java/org/springframework/http/codec/HttpDecoder.java rename to spring-web/src/main/java/org/springframework/http/codec/HttpMessageDecoder.java index 146bd9ce94..43542af563 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/HttpDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageDecoder.java @@ -25,12 +25,12 @@ import org.springframework.http.server.reactive.ServerHttpResponse; /** * Extension of {@code Decoder} exposing extra methods relevant in the context - * of HTTP applications. + * of HTTP request or response body decoding. * * @author Rossen Stoyanchev * @since 5.0 */ -public interface HttpDecoder extends Decoder { +public interface HttpMessageDecoder extends Decoder { /** * Get decoding hints based on the server request or annotations on the diff --git a/spring-web/src/main/java/org/springframework/http/codec/HttpEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageEncoder.java similarity index 95% rename from spring-web/src/main/java/org/springframework/http/codec/HttpEncoder.java rename to spring-web/src/main/java/org/springframework/http/codec/HttpMessageEncoder.java index 98b3f94357..bb292acda4 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/HttpEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageEncoder.java @@ -29,12 +29,12 @@ import org.springframework.http.server.reactive.ServerHttpResponse; /** * Extension of {@code Encoder} exposing extra methods relevant in the context - * of HTTP applications. + * of HTTP request or response body encoding. * * @author Rossen Stoyanchev * @since 5.0 */ -public interface HttpEncoder extends Encoder { +public interface HttpMessageEncoder extends Encoder { /** * Return "streaming" media types for which flushing should be performed diff --git a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java index 009de9b0d8..0fbc036deb 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java @@ -22,9 +22,12 @@ import java.util.Map; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.http.MediaType; import org.springframework.http.ReactiveHttpInputMessage; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; /** * Strategy for reading from a {@link ReactiveHttpInputMessage} and decoding @@ -74,5 +77,44 @@ public interface HttpMessageReader { */ Mono readMono(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints); + /** + * Server-side only alternative to + * {@link #read(ResolvableType, ReactiveHttpInputMessage, Map)} with + * additional context available. + * + * @param actualType the actual type of the target method parameter; for + * annotated controllers, the {@link MethodParameter} can be accessed via + * {@link ResolvableType#getSource()}. + * @param elementType the type of Objects in the output stream + * @param request the current request + * @param response the current response + * @param hints additional information about how to read the body + * @return the decoded stream of elements + */ + default Flux read(ResolvableType actualType, ResolvableType elementType, ServerHttpRequest request, + ServerHttpResponse response, Map hints) { + + return read(elementType, request, hints); + } + + /** + * Server-side only alternative to + * {@link #readMono(ResolvableType, ReactiveHttpInputMessage, Map)} with + * additional, context available. + * + * @param actualType the actual type of the target method parameter; for + * annotated controllers, the {@link MethodParameter} can be accessed via + * {@link ResolvableType#getSource()}. + * @param elementType the type of Objects in the output stream + * @param request the current request + * @param response the current response + * @param hints additional information about how to read the body + * @return the decoded stream of elements + */ + default Mono readMono(ResolvableType actualType, ResolvableType elementType, ServerHttpRequest request, + ServerHttpResponse response, Map hints) { + + return readMono(elementType, request, hints); + } } diff --git a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java index 6083f905fa..848341cd3a 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java @@ -22,9 +22,12 @@ import java.util.Map; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; +import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.http.MediaType; import org.springframework.http.ReactiveHttpOutputMessage; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; /** * Strategy for encoding a stream of objects of type {@code } and writing @@ -67,4 +70,26 @@ public interface HttpMessageWriter { MediaType mediaType, ReactiveHttpOutputMessage message, Map hints); + /** + * Server-side only alternative to + * {@link #write(Publisher, ResolvableType, MediaType, ReactiveHttpOutputMessage, Map)} + * with additional context available. + * + * @param actualType the actual return type of the method that returned the + * value; for annotated controllers, the {@link MethodParameter} can be + * accessed via {@link ResolvableType#getSource()}. + * @param elementType the type of Objects in the input stream + * @param mediaType the content type to use, possibly {@code null} indicating + * the default content type of the writer should be used. + * @param request the current request + * @param response the current response + * @return a {@link Mono} that indicates completion of writing or error + */ + default Mono write(Publisher inputStream, ResolvableType actualType, + ResolvableType elementType, MediaType mediaType, ServerHttpRequest request, + ServerHttpResponse response, Map hints) { + + return write(inputStream, elementType, mediaType, response, hints); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java index d074e9d234..a5da877917 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java @@ -53,7 +53,7 @@ import static java.util.Collections.emptyMap; /** * {@code HttpMessageWriter} that can write a {@link Resource}. * - *

Also an implementation of {@code ServerHttpMessageWriter} with support + *

Also an implementation of {@code HttpMessageWriter} with support * for writing one or more {@link ResourceRegion}'s based on the HTTP ranges * specified in the request. * @@ -68,7 +68,7 @@ import static java.util.Collections.emptyMap; * @see ResourceRegionEncoder * @see HttpRange */ -public class ResourceHttpMessageWriter implements ServerHttpMessageWriter { +public class ResourceHttpMessageWriter implements HttpMessageWriter { private static final ResolvableType REGION_TYPE = ResolvableType.forClass(ResourceRegion.class); @@ -102,7 +102,7 @@ public class ResourceHttpMessageWriter implements ServerHttpMessageWriter write(Publisher inputStream, ResolvableType elementType, @@ -171,7 +171,7 @@ public class ResourceHttpMessageWriter implements ServerHttpMessageWriterThe built-in defaults include basic data types such as + * {@link Byte byte[]}, {@link java.nio.ByteBuffer ByteBuffer}, + * {@link org.springframework.core.io.buffer.DataBuffer DataBuffer}, + * {@link String}, {@link org.springframework.core.io.Resource Resource}, + * in addition to JAXB2 and Jackson 2 based on classpath detection, as well as + * support for Server-Sent Events. There are options to {@link #defaultCodec() + * override} some of the defaults or to have them + * {@link #registerDefaults(boolean) turned off} completely. + * + * @author Rossen Stoyanchev + * @since 5.0 + */ +public class ServerCodecConfigurer { + + private static final boolean jackson2Present = + ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", + ServerCodecConfigurer.class.getClassLoader()) && + ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", + ServerCodecConfigurer.class.getClassLoader()); + + private static final boolean jaxb2Present = + ClassUtils.isPresent("javax.xml.bind.Binder", ServerCodecConfigurer.class.getClassLoader()); + + + private final DefaultCodecConfigurer defaultCodecs = new DefaultCodecConfigurer(); + + private final CustomCodecConfigurer customCodecs = new CustomCodecConfigurer(); + + + /** + * Provide overrides for built-in HTTP message readers and writers. + */ + public DefaultCodecConfigurer defaultCodec() { + return this.defaultCodecs; + } + + /** + * Whether to make default HTTP message reader and writer registrations. + *

By default this is set to {@code "true"}. + */ + public void registerDefaults(boolean register) { + this.defaultCodec().setSuppressed(!register); + } + + /** + * Register a custom encoder or decoder. + */ + public CustomCodecConfigurer customCodec() { + return this.customCodecs; + } + + + /** + * Prepare a list of HTTP message readers. + */ + public List> getReaders() { + + // Built-in, concrete Java type readers + List> result = new ArrayList<>(); + this.defaultCodecs.addReaderTo(result, ByteArrayDecoder.class, ByteArrayDecoder::new); + this.defaultCodecs.addReaderTo(result, ByteBufferDecoder.class, ByteBufferDecoder::new); + this.defaultCodecs.addReaderTo(result, DataBufferDecoder.class, DataBufferDecoder::new); + this.defaultCodecs.addReaderTo(result, ResourceDecoder.class, ResourceDecoder::new); + this.defaultCodecs.addStringReaderTextOnlyTo(result); + + // Custom, concrete Java type readers + this.customCodecs.addTypedReadersTo(result); + + // Built-in, Object-based readers + if (jaxb2Present) { + this.defaultCodecs.addReaderTo(result, Jaxb2XmlDecoder.class, Jaxb2XmlDecoder::new); + } + if (jackson2Present) { + this.defaultCodecs.addReaderTo(result, Jackson2JsonDecoder.class, Jackson2JsonDecoder::new); + } + + // Custom, Object-based readers + this.customCodecs.addObjectReadersTo(result); + + // Potentially overlapping Java types + "*/*" + this.defaultCodecs.addStringReaderTo(result); + return result; + } + + /** + * Prepare a list of HTTP message writers. + */ + public List> getWriters() { + + // Built-in, concrete Java type readers + List> result = new ArrayList<>(); + this.defaultCodecs.addWriterTo(result, ByteArrayEncoder.class, ByteArrayEncoder::new); + this.defaultCodecs.addWriterTo(result, ByteBufferEncoder.class, ByteBufferEncoder::new); + this.defaultCodecs.addWriterTo(result, DataBufferEncoder.class, DataBufferEncoder::new); + this.defaultCodecs.addWriterTo(result, ResourceHttpMessageWriter::new); + this.defaultCodecs.addStringWriterTextPlainOnlyTo(result); + + // Custom, concrete Java type readers + this.customCodecs.addTypedWritersTo(result); + + // Built-in, Object-based readers + if (jaxb2Present) { + this.defaultCodecs.addWriterTo(result, Jaxb2XmlEncoder.class, Jaxb2XmlEncoder::new); + } + if (jackson2Present) { + this.defaultCodecs.addWriterTo(result, Jackson2JsonEncoder.class, Jackson2JsonEncoder::new); + } + this.defaultCodecs.addSseWriterTo(result); + + // Custom, Object-based readers + this.customCodecs.addObjectWritersTo(result); + + // Potentially overlapping Java types + "*/*" + this.defaultCodecs.addStringWriterTo(result); + return result; + } + + + /** + * A registry and a factory for built-in HTTP message readers and writers. + */ + public static class DefaultCodecConfigurer { + + private boolean suppressed = false; + + private final Map, HttpMessageReader> readers = new HashMap<>(); + + private final Map, HttpMessageWriter> writers = new HashMap<>(); + + + /** + * Override the default Jackson {@code Decoder}. + * @param decoder the decoder to use + */ + public void jackson2Decoder(Jackson2JsonDecoder decoder) { + this.readers.put(Jackson2JsonDecoder.class, new DecoderHttpMessageReader<>(decoder)); + } + + /** + * Override the default Jackson {@code Encoder} for JSON. Also used for + * SSE unless further overridden via {@link #sse(Encoder)}. + * @param encoder the encoder to use + */ + public void jackson2Encoder(Jackson2JsonEncoder encoder) { + this.writers.put(Jackson2JsonEncoder.class, new EncoderHttpMessageWriter<>(encoder)); + } + + /** + * 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 + */ + public void sse(Encoder encoder) { + HttpMessageWriter writer = new ServerSentEventHttpMessageWriter(encoder); + this.writers.put(ServerSentEventHttpMessageWriter.class, writer); + } + + + // Internal methods for building a list of default readers or writers... + + private void setSuppressed(boolean suppressed) { + this.suppressed = suppressed; + } + + private > void addReaderTo(List> result, + Class key, Supplier fallback) { + + addReaderTo(result, () -> findReader(key, fallback)); + } + + private void addReaderTo(List> result, + Supplier> reader) { + + if (!this.suppressed) { + result.add(reader.get()); + } + } + + private > DecoderHttpMessageReader findReader( + Class key, Supplier fallback) { + + DecoderHttpMessageReader reader = (DecoderHttpMessageReader) this.readers.get(key); + return reader != null ? reader : new DecoderHttpMessageReader<>(fallback.get()); + } + + + private > void addWriterTo(List> result, + Class key, Supplier fallback) { + + addWriterTo(result, () -> findWriter(key, fallback)); + } + + private void addWriterTo(List> result, + Supplier> writer) { + + if (!this.suppressed) { + result.add(writer.get()); + } + } + + private > EncoderHttpMessageWriter findWriter( + Class key, Supplier fallback) { + + EncoderHttpMessageWriter writer = (EncoderHttpMessageWriter) this.writers.get(key); + return writer != null ? writer : new EncoderHttpMessageWriter<>(fallback.get()); + } + + + private void addStringReaderTextOnlyTo(List> result) { + addReaderTo(result, () -> new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly(true))); + } + + private void addStringReaderTo(List> result) { + addReaderTo(result, () -> new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true))); + } + + private void addStringWriterTextPlainOnlyTo(List> result) { + addWriterTo(result, () -> new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly())); + } + + private void addStringWriterTo(List> result) { + addWriterTo(result, () -> new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes())); + } + + private void addSseWriterTo(List> result) { + addWriterTo(result, () -> { + HttpMessageWriter writer = this.writers.get(ServerSentEventHttpMessageWriter.class); + if (writer != null) { + return writer; + } + if (jackson2Present) { + return new ServerSentEventHttpMessageWriter( + findWriter(Jackson2JsonEncoder.class, Jackson2JsonEncoder::new).getEncoder()); + } + return new ServerSentEventHttpMessageWriter(); + }); + } + } + + /** + * Registry and container for custom HTTP message readers and writers. + */ + public static class CustomCodecConfigurer { + + private final List> typedReaders = new ArrayList<>(); + + private final List> typedWriters = new ArrayList<>(); + + private final List> objectReaders = new ArrayList<>(); + + private final List> objectWriters = new ArrayList<>(); + + + /** + * Add a custom {@code Decoder} internally wrapped with + * {@link DecoderHttpMessageReader}). + */ + public void decoder(Decoder decoder) { + reader(new DecoderHttpMessageReader<>(decoder)); + } + + /** + * Add a custom {@code Encoder}, internally wrapped with + * {@link EncoderHttpMessageWriter}. + */ + public void encoder(Encoder encoder) { + writer(new EncoderHttpMessageWriter<>(encoder)); + } + + /** + * Add a custom {@link HttpMessageReader}. For readers of type + * {@link DecoderHttpMessageReader} consider using the shortcut + * {@link #decoder(Decoder)} instead. + */ + public void reader(HttpMessageReader reader) { + boolean canReadToObject = reader.canRead(ResolvableType.forClass(Object.class), null); + (canReadToObject ? this.objectReaders : this.typedReaders).add(reader); + } + + /** + * Add a custom {@link HttpMessageWriter}. For readers of type + * {@link EncoderHttpMessageWriter} consider using the shortcut + * {@link #encoder(Encoder)} instead. + */ + public void writer(HttpMessageWriter writer) { + boolean canWriteObject = writer.canWrite(ResolvableType.forClass(Object.class), null); + (canWriteObject ? this.objectWriters : this.typedWriters).add(writer); + } + + + // Internal methods for building a list of custom readers or writers... + + private void addTypedReadersTo(List> result) { + result.addAll(this.typedReaders); + } + + private void addObjectReadersTo(List> result) { + result.addAll(this.objectReaders); + } + + private void addTypedWritersTo(List> result) { + result.addAll(this.typedWriters); + } + + private void addObjectWritersTo(List> result) { + result.addAll(this.objectWriters); + } + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageReader.java deleted file mode 100644 index 1509b42ddd..0000000000 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageReader.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2002-2017 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.http.codec; - -import java.util.Map; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.core.MethodParameter; -import org.springframework.core.ResolvableType; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; - -/** - * An extension of {@code HttpMessageReader} for decoding and reading the - * request body with extra information available on the server side. - * - * @author Sebastien Deleuze - * @author Rossen Stoyanchev - * @since 5.0 - */ -public interface ServerHttpMessageReader extends HttpMessageReader { - - /** - * Decode and read the request body to an object stream. - * - * @param actualType the actual type of the target method parameter; for - * annotated controllers, the {@link MethodParameter} can be accessed via - * {@link ResolvableType#getSource()}. - * @param elementType the type of Objects in the output stream - * @param request the current request - * @param response the current response - * @param hints additional information about how to read the body - * @return the decoded stream of elements - */ - Flux read(ResolvableType actualType, ResolvableType elementType, ServerHttpRequest request, - ServerHttpResponse response, Map hints); - - /** - * Decode and read the request body to a single object. - * - * @param actualType the actual type of the target method parameter; for - * annotated controllers, the {@link MethodParameter} can be accessed via - * {@link ResolvableType#getSource()}. - * @param elementType the type of Objects in the output stream - * @param request the current request - * @param response the current response - * @param hints additional information about how to read the body - * @return the decoded stream of elements - */ - Mono readMono(ResolvableType actualType, ResolvableType elementType, ServerHttpRequest request, - ServerHttpResponse response, Map hints); - -} diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageWriter.java deleted file mode 100644 index 5758d728c5..0000000000 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageWriter.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2002-2017 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.http.codec; - -import java.util.Map; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; - -import org.springframework.core.MethodParameter; -import org.springframework.core.ResolvableType; -import org.springframework.http.MediaType; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; - -/** - * An extension of {@code HttpMessageWriter} for encoding and writing the - * response body with extra information available on the server side. - * - * @author Sebastien Deleuze - * @author Rossen Stoyanchev - * @since 5.0 - */ -public interface ServerHttpMessageWriter extends HttpMessageWriter { - - /** - * Encode and write the given object stream to the response. - * - * @param actualType the actual return type of the method that returned the - * value; for annotated controllers, the {@link MethodParameter} can be - * accessed via {@link ResolvableType#getSource()}. - * @param elementType the type of Objects in the input stream - * @param mediaType the content type to use, possibly {@code null} indicating - * the default content type of the writer should be used. - * @param request the current request - * @param response the current response - * @return a {@link Mono} that indicates completion of writing or error - */ - Mono write(Publisher inputStream, ResolvableType actualType, - ResolvableType elementType, MediaType mediaType, ServerHttpRequest request, - ServerHttpResponse response, Map hints); - -} diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java index bfe2c42bcb..1fad05f0a3 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java @@ -37,14 +37,14 @@ import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; /** - * {@code ServerHttpMessageWriter} for {@code "text/event-stream"} responses. + * {@code HttpMessageWriter} for {@code "text/event-stream"} responses. * * @author Sebastien Deleuze * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 5.0 */ -public class ServerSentEventHttpMessageWriter implements ServerHttpMessageWriter { +public class ServerSentEventHttpMessageWriter implements HttpMessageWriter { private static final List WRITABLE_MEDIA_TYPES = Collections.singletonList(MediaType.TEXT_EVENT_STREAM); @@ -172,8 +172,8 @@ public class ServerSentEventHttpMessageWriter implements ServerHttpMessageWriter private Map getEncodeHints(ResolvableType actualType, ResolvableType elementType, MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) { - if (this.encoder instanceof HttpEncoder) { - HttpEncoder httpEncoder = (HttpEncoder) this.encoder; + if (this.encoder instanceof HttpMessageEncoder) { + HttpMessageEncoder httpEncoder = (HttpMessageEncoder) this.encoder; return httpEncoder.getEncodeHints(actualType, elementType, mediaType, request, response); } return Collections.emptyMap(); diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java index d3d2b94051..9beda0e8a7 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java @@ -33,7 +33,7 @@ import org.springframework.core.ResolvableType; import org.springframework.core.codec.CodecException; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.http.codec.HttpDecoder; +import org.springframework.http.codec.HttpMessageDecoder; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -48,7 +48,7 @@ import org.springframework.util.MimeType; * @since 5.0 * @see Jackson2JsonEncoder */ -public class Jackson2JsonDecoder extends Jackson2CodecSupport implements HttpDecoder { +public class Jackson2JsonDecoder extends Jackson2CodecSupport implements HttpMessageDecoder { private final JsonObjectDecoder fluxDecoder = new JsonObjectDecoder(true); @@ -120,7 +120,7 @@ public class Jackson2JsonDecoder extends Jackson2CodecSupport implements HttpDec } - // HttpDecoder... + // HttpMessageDecoder... @Override public Map getDecodeHints(ResolvableType actualType, ResolvableType elementType, diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java index 26a712afc0..0675ca09e2 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java @@ -42,7 +42,7 @@ import org.springframework.core.codec.CodecException; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.MediaType; -import org.springframework.http.codec.HttpEncoder; +import org.springframework.http.codec.HttpMessageEncoder; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -59,7 +59,7 @@ import org.springframework.util.MimeType; * @since 5.0 * @see Jackson2JsonDecoder */ -public class Jackson2JsonEncoder extends Jackson2CodecSupport implements HttpEncoder { +public class Jackson2JsonEncoder extends Jackson2CodecSupport implements HttpMessageEncoder { private final List streamingMediaTypes = new ArrayList<>(1); @@ -89,7 +89,7 @@ public class Jackson2JsonEncoder extends Jackson2CodecSupport implements HttpEnc * automatically vs at the end of the stream. *

By default this is set to {@link MediaType#APPLICATION_STREAM_JSON}. * @param mediaTypes one or more media types to add to the list - * @see HttpEncoder#getStreamingMediaTypes() + * @see HttpMessageEncoder#getStreamingMediaTypes() */ public void setStreamingMediaTypes(List mediaTypes) { this.streamingMediaTypes.clear(); @@ -169,7 +169,7 @@ public class Jackson2JsonEncoder extends Jackson2CodecSupport implements HttpEnc } - // HttpEncoder... + // HttpMessageEncoder... @Override public List getStreamingMediaTypes() { diff --git a/spring-web/src/test/java/org/springframework/http/codec/ServerCodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/ServerCodecConfigurerTests.java new file mode 100644 index 0000000000..b9ed7694d3 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/codec/ServerCodecConfigurerTests.java @@ -0,0 +1,299 @@ +/* + * Copyright 2002-2017 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.http.codec; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import org.springframework.core.ResolvableType; +import org.springframework.core.codec.ByteArrayDecoder; +import org.springframework.core.codec.ByteArrayEncoder; +import org.springframework.core.codec.ByteBufferDecoder; +import org.springframework.core.codec.ByteBufferEncoder; +import org.springframework.core.codec.CharSequenceEncoder; +import org.springframework.core.codec.DataBufferDecoder; +import org.springframework.core.codec.DataBufferEncoder; +import org.springframework.core.codec.Decoder; +import org.springframework.core.codec.Encoder; +import org.springframework.core.codec.ResourceDecoder; +import org.springframework.core.codec.StringDecoder; +import org.springframework.http.MediaType; +import org.springframework.http.codec.json.Jackson2JsonDecoder; +import org.springframework.http.codec.json.Jackson2JsonEncoder; +import org.springframework.http.codec.xml.Jaxb2XmlDecoder; +import org.springframework.http.codec.xml.Jaxb2XmlEncoder; +import org.springframework.util.MimeTypeUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.core.ResolvableType.forClass; + +/** + * Unit tests for {@link ServerCodecConfigurer}. + * @author Rossen Stoyanchev + */ +public class ServerCodecConfigurerTests { + + private final ServerCodecConfigurer configurer = new ServerCodecConfigurer(); + + private final AtomicInteger index = new AtomicInteger(0); + + + @Test + public void defaultReaders() throws Exception { + List> readers = this.configurer.getReaders(); + assertEquals(8, readers.size()); + assertEquals(ByteArrayDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(ByteBufferDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(DataBufferDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(ResourceDecoder.class, getNextDecoder(readers).getClass()); + assertStringDecoder(getNextDecoder(readers), true); + assertEquals(Jaxb2XmlDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(Jackson2JsonDecoder.class, getNextDecoder(readers).getClass()); + assertStringDecoder(getNextDecoder(readers), false); + } + + @Test + public void defaultWriters() throws Exception { + List> writers = this.configurer.getWriters(); + assertEquals(9, writers.size()); + assertEquals(ByteArrayEncoder.class, getNextEncoder(writers).getClass()); + assertEquals(ByteBufferEncoder.class, getNextEncoder(writers).getClass()); + assertEquals(DataBufferEncoder.class, getNextEncoder(writers).getClass()); + assertEquals(ResourceHttpMessageWriter.class, writers.get(index.getAndIncrement()).getClass()); + assertStringEncoder(getNextEncoder(writers), true); + assertEquals(Jaxb2XmlEncoder.class, getNextEncoder(writers).getClass()); + assertEquals(Jackson2JsonEncoder.class, getNextEncoder(writers).getClass()); + assertSseWriter(writers); + assertStringEncoder(getNextEncoder(writers), false); + } + + @Test + public void defaultAndCustomReaders() throws Exception { + + Decoder customDecoder1 = mock(Decoder.class); + Decoder customDecoder2 = mock(Decoder.class); + + when(customDecoder1.canDecode(ResolvableType.forClass(Object.class), null)).thenReturn(false); + when(customDecoder2.canDecode(ResolvableType.forClass(Object.class), null)).thenReturn(true); + + HttpMessageReader customReader1 = mock(HttpMessageReader.class); + HttpMessageReader customReader2 = mock(HttpMessageReader.class); + + when(customReader1.canRead(ResolvableType.forClass(Object.class), null)).thenReturn(false); + when(customReader2.canRead(ResolvableType.forClass(Object.class), null)).thenReturn(true); + + this.configurer.customCodec().decoder(customDecoder1); + this.configurer.customCodec().decoder(customDecoder2); + + this.configurer.customCodec().reader(customReader1); + this.configurer.customCodec().reader(customReader2); + + List> readers = this.configurer.getReaders(); + + assertEquals(12, readers.size()); + assertEquals(ByteArrayDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(ByteBufferDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(DataBufferDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(ResourceDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(StringDecoder.class, getNextDecoder(readers).getClass()); + assertSame(customDecoder1, getNextDecoder(readers)); + assertSame(customReader1, readers.get(this.index.getAndIncrement())); + assertEquals(Jaxb2XmlDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(Jackson2JsonDecoder.class, getNextDecoder(readers).getClass()); + assertSame(customDecoder2, getNextDecoder(readers)); + assertSame(customReader2, readers.get(this.index.getAndIncrement())); + assertEquals(StringDecoder.class, getNextDecoder(readers).getClass()); + } + + @Test + public void defaultAndCustomWriters() throws Exception { + + Encoder customEncoder1 = mock(Encoder.class); + Encoder customEncoder2 = mock(Encoder.class); + + when(customEncoder1.canEncode(ResolvableType.forClass(Object.class), null)).thenReturn(false); + when(customEncoder2.canEncode(ResolvableType.forClass(Object.class), null)).thenReturn(true); + + HttpMessageWriter customWriter1 = mock(HttpMessageWriter.class); + HttpMessageWriter customWriter2 = mock(HttpMessageWriter.class); + + when(customWriter1.canWrite(ResolvableType.forClass(Object.class), null)).thenReturn(false); + when(customWriter2.canWrite(ResolvableType.forClass(Object.class), null)).thenReturn(true); + + this.configurer.customCodec().encoder(customEncoder1); + this.configurer.customCodec().encoder(customEncoder2); + + this.configurer.customCodec().writer(customWriter1); + this.configurer.customCodec().writer(customWriter2); + + List> writers = this.configurer.getWriters(); + + assertEquals(13, writers.size()); + assertEquals(ByteArrayEncoder.class, getNextEncoder(writers).getClass()); + assertEquals(ByteBufferEncoder.class, getNextEncoder(writers).getClass()); + assertEquals(DataBufferEncoder.class, getNextEncoder(writers).getClass()); + assertEquals(ResourceHttpMessageWriter.class, writers.get(index.getAndIncrement()).getClass()); + assertEquals(CharSequenceEncoder.class, getNextEncoder(writers).getClass()); + assertSame(customEncoder1, getNextEncoder(writers)); + assertSame(customWriter1, writers.get(this.index.getAndIncrement())); + assertEquals(Jaxb2XmlEncoder.class, getNextEncoder(writers).getClass()); + assertEquals(Jackson2JsonEncoder.class, getNextEncoder(writers).getClass()); + assertEquals(ServerSentEventHttpMessageWriter.class, writers.get(this.index.getAndIncrement()).getClass()); + assertSame(customEncoder2, getNextEncoder(writers)); + assertSame(customWriter2, writers.get(this.index.getAndIncrement())); + assertEquals(CharSequenceEncoder.class, getNextEncoder(writers).getClass()); + } + + @Test + public void defaultsOffCustomReaders() throws Exception { + + Decoder customDecoder1 = mock(Decoder.class); + Decoder customDecoder2 = mock(Decoder.class); + + when(customDecoder1.canDecode(ResolvableType.forClass(Object.class), null)).thenReturn(false); + when(customDecoder2.canDecode(ResolvableType.forClass(Object.class), null)).thenReturn(true); + + HttpMessageReader customReader1 = mock(HttpMessageReader.class); + HttpMessageReader customReader2 = mock(HttpMessageReader.class); + + when(customReader1.canRead(ResolvableType.forClass(Object.class), null)).thenReturn(false); + when(customReader2.canRead(ResolvableType.forClass(Object.class), null)).thenReturn(true); + + this.configurer.customCodec().decoder(customDecoder1); + this.configurer.customCodec().decoder(customDecoder2); + + this.configurer.customCodec().reader(customReader1); + this.configurer.customCodec().reader(customReader2); + + this.configurer.registerDefaults(false); + + List> readers = this.configurer.getReaders(); + + assertEquals(4, readers.size()); + assertSame(customDecoder1, getNextDecoder(readers)); + assertSame(customReader1, readers.get(this.index.getAndIncrement())); + assertSame(customDecoder2, getNextDecoder(readers)); + assertSame(customReader2, readers.get(this.index.getAndIncrement())); + } + + @Test + public void defaultsOffWithCustomWriters() throws Exception { + + Encoder customEncoder1 = mock(Encoder.class); + Encoder customEncoder2 = mock(Encoder.class); + + when(customEncoder1.canEncode(ResolvableType.forClass(Object.class), null)).thenReturn(false); + when(customEncoder2.canEncode(ResolvableType.forClass(Object.class), null)).thenReturn(true); + + HttpMessageWriter customWriter1 = mock(HttpMessageWriter.class); + HttpMessageWriter customWriter2 = mock(HttpMessageWriter.class); + + when(customWriter1.canWrite(ResolvableType.forClass(Object.class), null)).thenReturn(false); + when(customWriter2.canWrite(ResolvableType.forClass(Object.class), null)).thenReturn(true); + + this.configurer.customCodec().encoder(customEncoder1); + this.configurer.customCodec().encoder(customEncoder2); + + this.configurer.customCodec().writer(customWriter1); + this.configurer.customCodec().writer(customWriter2); + + this.configurer.registerDefaults(false); + + List> writers = this.configurer.getWriters(); + + assertEquals(4, writers.size()); + assertSame(customEncoder1, getNextEncoder(writers)); + assertSame(customWriter1, writers.get(this.index.getAndIncrement())); + assertSame(customEncoder2, getNextEncoder(writers)); + assertSame(customWriter2, writers.get(this.index.getAndIncrement())); + } + + @Test + public void jackson2DecoderOverride() throws Exception { + + Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(); + this.configurer.defaultCodec().jackson2Decoder(decoder); + + assertSame(decoder, this.configurer.getReaders().stream() + .filter(writer -> writer instanceof DecoderHttpMessageReader) + .map(writer -> ((DecoderHttpMessageReader) writer).getDecoder()) + .filter(e -> Jackson2JsonDecoder.class.equals(e.getClass())) + .findFirst() + .filter(e -> e == decoder).orElse(null)); + } + + @Test + public void jackson2EncoderOverride() throws Exception { + + Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(); + this.configurer.defaultCodec().jackson2Encoder(encoder); + + assertSame(encoder, this.configurer.getWriters().stream() + .filter(writer -> writer instanceof EncoderHttpMessageWriter) + .map(writer -> ((EncoderHttpMessageWriter) writer).getEncoder()) + .filter(e -> Jackson2JsonEncoder.class.equals(e.getClass())) + .findFirst() + .filter(e -> e == encoder).orElse(null)); + + assertSame(encoder, this.configurer.getWriters().stream() + .filter(writer -> ServerSentEventHttpMessageWriter.class.equals(writer.getClass())) + .map(writer -> (ServerSentEventHttpMessageWriter) writer) + .findFirst() + .map(ServerSentEventHttpMessageWriter::getEncoder) + .filter(e -> e == encoder).orElse(null)); + } + + + private Decoder getNextDecoder(List> readers) { + HttpMessageReader reader = readers.get(this.index.getAndIncrement()); + assertEquals(DecoderHttpMessageReader.class, reader.getClass()); + return ((DecoderHttpMessageReader) reader).getDecoder(); + } + + private Encoder getNextEncoder(List> writers) { + HttpMessageWriter writer = writers.get(this.index.getAndIncrement()); + assertEquals(EncoderHttpMessageWriter.class, writer.getClass()); + return ((EncoderHttpMessageWriter) writer).getEncoder(); + } + + private void assertStringDecoder(Decoder decoder, boolean textOnly) { + assertEquals(StringDecoder.class, decoder.getClass()); + assertTrue(decoder.canDecode(forClass(String.class), MimeTypeUtils.TEXT_PLAIN)); + assertEquals(!textOnly, decoder.canDecode(forClass(String.class), MediaType.TEXT_EVENT_STREAM)); + } + + private void assertStringEncoder(Encoder encoder, boolean textOnly) { + assertEquals(CharSequenceEncoder.class, encoder.getClass()); + assertTrue(encoder.canEncode(forClass(String.class), MimeTypeUtils.TEXT_PLAIN)); + assertEquals(!textOnly, encoder.canEncode(forClass(String.class), MediaType.TEXT_EVENT_STREAM)); + } + + private void assertSseWriter(List> writers) { + HttpMessageWriter writer = writers.get(this.index.getAndIncrement()); + assertEquals(ServerSentEventHttpMessageWriter.class, writer.getClass()); + Encoder encoder = ((ServerSentEventHttpMessageWriter) writer).getEncoder(); + assertNotNull(encoder); + assertEquals(Jackson2JsonEncoder.class, encoder.getClass()); + } + +} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java index 18cb061f3e..a986150401 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java @@ -21,8 +21,7 @@ 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.ServerHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.util.CollectionUtils; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; @@ -76,13 +75,8 @@ public class DelegatingWebFluxConfiguration extends WebFluxConfigurationSupport } @Override - protected void configureMessageReaders(List> messageReaders) { - this.configurers.configureMessageReaders(messageReaders); - } - - @Override - protected void extendMessageReaders(List> messageReaders) { - this.configurers.extendMessageReaders(messageReaders); + protected void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { + this.configurers.configureHttpMessageCodecs(configurer); } @Override @@ -100,16 +94,6 @@ public class DelegatingWebFluxConfiguration extends WebFluxConfigurationSupport 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-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 fc657dfa12..c8f6ed9a7c 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 @@ -30,32 +30,13 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.annotation.Order; -import org.springframework.core.codec.ByteArrayDecoder; -import org.springframework.core.codec.ByteArrayEncoder; -import org.springframework.core.codec.ByteBufferDecoder; -import org.springframework.core.codec.ByteBufferEncoder; -import org.springframework.core.codec.CharSequenceEncoder; -import org.springframework.core.codec.DataBufferDecoder; -import org.springframework.core.codec.DataBufferEncoder; -import org.springframework.core.codec.Encoder; -import org.springframework.core.codec.ResourceDecoder; -import org.springframework.core.codec.StringDecoder; import org.springframework.core.convert.converter.Converter; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.MediaType; -import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.EncoderHttpMessageWriter; -import org.springframework.http.codec.ResourceHttpMessageWriter; -import org.springframework.http.codec.ServerHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageWriter; -import org.springframework.http.codec.ServerSentEventHttpMessageWriter; -import org.springframework.http.codec.json.Jackson2JsonDecoder; -import org.springframework.http.codec.json.Jackson2JsonEncoder; -import org.springframework.http.codec.xml.Jaxb2XmlDecoder; -import org.springframework.http.codec.xml.Jaxb2XmlEncoder; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.util.ClassUtils; import org.springframework.validation.Errors; import org.springframework.validation.MessageCodesResolver; @@ -104,9 +85,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { private PathMatchConfigurer pathMatchConfigurer; - private List> messageReaders; - - private List> messageWriters; + private ServerCodecConfigurer messageCodecsConfigurer; private ApplicationContext applicationContext; @@ -267,7 +246,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); - adapter.setMessageReaders(getMessageReaders()); + adapter.setMessageReaders(getMessageCodecsConfigurer().getReaders()); adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer()); adapter.setReactiveAdapterRegistry(webFluxAdapterRegistry()); @@ -294,58 +273,22 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { } /** - * Main method to access message readers to use for decoding - * controller method arguments with. - *

Use {@link #configureMessageReaders} to configure the list or - * {@link #extendMessageReaders} to add in addition to the default ones. + * Main method to access the configurer for HTTP message readers and writers. + *

Use {@link #configureHttpMessageCodecs(ServerCodecConfigurer)} to + * configure the readers and writers. */ - protected final List> getMessageReaders() { - if (this.messageReaders == null) { - this.messageReaders = new ArrayList<>(); - configureMessageReaders(this.messageReaders); - if (this.messageReaders.isEmpty()) { - addDefaultHttpMessageReaders(this.messageReaders); - } - extendMessageReaders(this.messageReaders); + protected final ServerCodecConfigurer getMessageCodecsConfigurer() { + if (this.messageCodecsConfigurer == null) { + this.messageCodecsConfigurer = new ServerCodecConfigurer(); + configureHttpMessageCodecs(this.getMessageCodecsConfigurer()); } - return this.messageReaders; + return this.messageCodecsConfigurer; } /** - * Override to configure the message readers to use for decoding - * controller method arguments. - *

If no message readres are specified, default will be added via - * {@link #addDefaultHttpMessageReaders}. - * @param messageReaders a list to add message readers to, initially an empty + * Override to configure the HTTP message readers and writers to use. */ - protected void configureMessageReaders(List> messageReaders) { - } - - /** - * Adds default converters that sub-classes can call from - * {@link #configureMessageReaders(List)} for {@code byte[]}, - * {@code ByteBuffer}, {@code String}, {@code Resource}, JAXB2, and Jackson - * (if present on the classpath). - */ - protected final void addDefaultHttpMessageReaders(List> readers) { - readers.add(new DecoderHttpMessageReader<>(new ByteArrayDecoder())); - readers.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder())); - readers.add(new DecoderHttpMessageReader<>(new DataBufferDecoder())); - readers.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true))); - readers.add(new DecoderHttpMessageReader<>(new ResourceDecoder())); - if (jaxb2Present) { - readers.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder())); - } - if (jackson2Present) { - readers.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder())); - } - } - - /** - * Override this to modify the list of message readers after it has been - * configured, for example to add some in addition to the default ones. - */ - protected void extendMessageReaders(List> messageReaders) { + protected void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { } /** @@ -435,76 +378,14 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { @Bean public ResponseEntityResultHandler responseEntityResultHandler() { - return new ResponseEntityResultHandler( - getMessageWriters(), webFluxContentTypeResolver(), webFluxAdapterRegistry()); + return new ResponseEntityResultHandler(getMessageCodecsConfigurer().getWriters(), + webFluxContentTypeResolver(), webFluxAdapterRegistry()); } @Bean public ResponseBodyResultHandler responseBodyResultHandler() { - return new ResponseBodyResultHandler( - getMessageWriters(), webFluxContentTypeResolver(), webFluxAdapterRegistry()); - } - - /** - * Main method to access message writers to use for encoding return values. - *

Use {@link #configureMessageWriters(List)} to configure the list or - * {@link #extendMessageWriters(List)} to add in addition to the default ones. - */ - protected final List> getMessageWriters() { - if (this.messageWriters == null) { - this.messageWriters = new ArrayList<>(); - configureMessageWriters(this.messageWriters); - if (this.messageWriters.isEmpty()) { - addDefaultHttpMessageWriters(this.messageWriters); - } - extendMessageWriters(this.messageWriters); - } - return this.messageWriters; - } - /** - * Override to configure the message writers to use for encoding - * return values. - *

If no message readers are specified, default will be added via - * {@link #addDefaultHttpMessageWriters}. - * @param messageWriters a list to add message writers to, initially an empty - */ - protected void configureMessageWriters(List> messageWriters) { - } - - /** - * Adds default converters that sub-classes can call from - * {@link #configureMessageWriters(List)}. - */ - protected final void addDefaultHttpMessageWriters(List> writers) { - writers.add(new EncoderHttpMessageWriter<>(new ByteArrayEncoder())); - writers.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); - writers.add(new EncoderHttpMessageWriter<>(new DataBufferEncoder())); - writers.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly())); - writers.add(new ResourceHttpMessageWriter()); - if (jaxb2Present) { - writers.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder())); - } - if (jackson2Present) { - writers.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder())); - } - writers.add(new ServerSentEventHttpMessageWriter(getSseEncoder())); - writers.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes())); - } - - private Encoder getSseEncoder() { - if (jackson2Present) { - return new Jackson2JsonEncoder(); - } - else { - return null; - } - } - - /** - * 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. - */ - protected void extendMessageWriters(List> messageWriters) { + return new ResponseBodyResultHandler(getMessageCodecsConfigurer().getWriters(), + webFluxContentTypeResolver(), webFluxAdapterRegistry()); } @Bean diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java index fdf29d8f54..1f51f69901 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java @@ -22,8 +22,7 @@ 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.ServerHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; import org.springframework.web.reactive.accept.CompositeContentTypeResolver; @@ -89,22 +88,10 @@ public interface WebFluxConfigurer { } /** - * Configure the message readers to use for decoding the request body where - * {@code @RequestBody} and {@code HttpEntity} controller method arguments - * are used. If none are specified, default ones are added based on - * {@link WebFluxConfigurationSupport#addDefaultHttpMessageReaders}. - *

See {@link #extendMessageReaders(List)} for adding readers - * in addition to the default ones. - * @param readers an empty list to add message readers to + * Configure custom HTTP message readers and writers or override built-in ones. + * @param configurer the configurer to use */ - default void configureMessageReaders(List> readers) { - } - - /** - * An alternative to {@link #configureMessageReaders(List)} that allows - * modifying the message readers to use after default ones have been added. - */ - default void extendMessageReaders(List> readers) { + default void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { } /** @@ -132,25 +119,6 @@ public interface WebFluxConfigurer { return Optional.empty(); } - /** - * Configure the message writers to use to encode the response body based on - * the return values of {@code @ResponseBody}, and {@code ResponseEntity} - * controller methods. If none are specified, default ones are added based on - * {@link WebFluxConfigurationSupport#addDefaultHttpMessageWriters(List)}. - *

See {@link #extendMessageWriters(List)} for adding writers - * in addition to the default ones. - * @param writers a empty list to add message writers to - */ - default void configureMessageWriters(List> writers) { - } - - /** - * An alternative to {@link #configureMessageWriters(List)} that allows - * modifying the message writers to use after default ones have been added. - */ - default void extendMessageWriters(List> writers) { - } - /** * Configure view resolution for processing the return values of controller * methods that rely on resolving a diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java index 51070f12c0..bfb92ff1bb 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java @@ -23,8 +23,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.format.FormatterRegistry; -import org.springframework.http.codec.ServerHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.util.CollectionUtils; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; @@ -52,42 +51,37 @@ public class WebFluxConfigurerComposite implements WebFluxConfigurer { @Override public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) { - this.delegates.stream().forEach(delegate -> delegate.configureContentTypeResolver(builder)); + this.delegates.forEach(delegate -> delegate.configureContentTypeResolver(builder)); } @Override public void addCorsMappings(CorsRegistry registry) { - this.delegates.stream().forEach(delegate -> delegate.addCorsMappings(registry)); + this.delegates.forEach(delegate -> delegate.addCorsMappings(registry)); } @Override public void configurePathMatching(PathMatchConfigurer configurer) { - this.delegates.stream().forEach(delegate -> delegate.configurePathMatching(configurer)); + this.delegates.forEach(delegate -> delegate.configurePathMatching(configurer)); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { - this.delegates.stream().forEach(delegate -> delegate.addResourceHandlers(registry)); + this.delegates.forEach(delegate -> delegate.addResourceHandlers(registry)); } @Override public void addArgumentResolvers(List resolvers) { - this.delegates.stream().forEach(delegate -> delegate.addArgumentResolvers(resolvers)); + this.delegates.forEach(delegate -> delegate.addArgumentResolvers(resolvers)); } @Override - public void configureMessageReaders(List> readers) { - this.delegates.stream().forEach(delegate -> delegate.configureMessageReaders(readers)); - } - - @Override - public void extendMessageReaders(List> readers) { - this.delegates.stream().forEach(delegate -> delegate.extendMessageReaders(readers)); + public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { + this.delegates.forEach(delegate -> delegate.configureHttpMessageCodecs(configurer)); } @Override public void addFormatters(FormatterRegistry registry) { - this.delegates.stream().forEach(delegate -> delegate.addFormatters(registry)); + this.delegates.forEach(delegate -> delegate.addFormatters(registry)); } @Override @@ -100,19 +94,9 @@ public class WebFluxConfigurerComposite implements WebFluxConfigurer { return createSingleBean(WebFluxConfigurer::getMessageCodesResolver, MessageCodesResolver.class); } - @Override - public void configureMessageWriters(List> writers) { - this.delegates.stream().forEach(delegate -> delegate.configureMessageWriters(writers)); - } - - @Override - public void extendMessageWriters(List> writers) { - this.delegates.stream().forEach(delegate -> delegate.extendMessageWriters(writers)); - } - @Override public void configureViewResolvers(ViewResolverRegistry registry) { - this.delegates.stream().forEach(delegate -> delegate.configureViewResolvers(registry)); + this.delegates.forEach(delegate -> delegate.configureViewResolvers(registry)); } private Optional createSingleBean(Function> factory, 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 218b8df555..2d2d334149 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 @@ -26,26 +26,10 @@ import java.util.function.Supplier; import java.util.stream.Stream; import org.springframework.context.ApplicationContext; -import org.springframework.core.codec.ByteArrayDecoder; -import org.springframework.core.codec.ByteArrayEncoder; -import org.springframework.core.codec.ByteBufferDecoder; -import org.springframework.core.codec.ByteBufferEncoder; -import org.springframework.core.codec.CharSequenceEncoder; -import org.springframework.core.codec.Encoder; -import org.springframework.core.codec.StringDecoder; -import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.EncoderHttpMessageWriter; -import org.springframework.http.codec.FormHttpMessageReader; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; -import org.springframework.http.codec.ResourceHttpMessageWriter; -import org.springframework.http.codec.ServerSentEventHttpMessageWriter; -import org.springframework.http.codec.json.Jackson2JsonDecoder; -import org.springframework.http.codec.json.Jackson2JsonEncoder; -import org.springframework.http.codec.xml.Jaxb2XmlDecoder; -import org.springframework.http.codec.xml.Jaxb2XmlEncoder; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.web.reactive.result.view.ViewResolver; /** @@ -61,16 +45,6 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { .map(Locale.LanguageRange::getRange) .map(Locale::forLanguageTag).findFirst(); - private static final boolean jackson2Present = - ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", - DefaultHandlerStrategiesBuilder.class.getClassLoader()) && - ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", - DefaultHandlerStrategiesBuilder.class.getClassLoader()); - - private static final boolean jaxb2Present = - ClassUtils.isPresent("javax.xml.bind.Binder", - DefaultHandlerStrategiesBuilder.class.getClassLoader()); - private final List> messageReaders = new ArrayList<>(); @@ -82,40 +56,12 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { public void defaultConfiguration() { - messageReader(new DecoderHttpMessageReader<>(new ByteArrayDecoder())); - messageReader(new DecoderHttpMessageReader<>(new ByteBufferDecoder())); - messageReader(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true))); - messageReader(new FormHttpMessageReader()); - - messageWriter(new EncoderHttpMessageWriter<>(new ByteArrayEncoder())); - messageWriter(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); - messageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly())); - messageWriter(new ResourceHttpMessageWriter()); - - if (jaxb2Present) { - messageReader(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder())); - messageWriter(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder())); - } - if (jackson2Present) { - messageReader(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder())); - messageWriter(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder())); - } - - messageWriter(new ServerSentEventHttpMessageWriter(getSseEncoder())); - messageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes())); - + ServerCodecConfigurer configurer = new ServerCodecConfigurer(); + configurer.getReaders().forEach(this::messageReader); + configurer.getWriters().forEach(this::messageWriter); localeResolver(DEFAULT_LOCALE_RESOLVER); } - private Encoder getSseEncoder() { - if (jackson2Present) { - return new Jackson2JsonEncoder(); - } - else { - return null; - } - } - public void applicationContext(ApplicationContext applicationContext) { applicationContext.getBeansOfType(HttpMessageReader.class).values().forEach(this::messageReader); applicationContext.getBeansOfType(HttpMessageWriter.class).values().forEach(this::messageWriter); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java index a833004b5b..b450bf727a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java @@ -32,7 +32,7 @@ import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.MediaType; -import org.springframework.http.codec.ServerHttpMessageReader; +import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; @@ -48,7 +48,7 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException; /** * Abstract base class for argument resolvers that resolve method arguments - * by reading the request body with an {@link ServerHttpMessageReader}. + * by reading the request body with an {@link HttpMessageReader}. * *

Applies validation if the method argument is annotated with * {@code @javax.validation.Valid} or @@ -60,16 +60,16 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException; */ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMethodArgumentResolverSupport { - private final List> messageReaders; + private final List> messageReaders; private final List supportedMediaTypes; /** - * Constructor with {@link ServerHttpMessageReader}'s and a {@link Validator}. + * Constructor with {@link HttpMessageReader}'s and a {@link Validator}. * @param readers readers to convert from the request body */ - protected AbstractMessageReaderArgumentResolver(List> readers) { + protected AbstractMessageReaderArgumentResolver(List> readers) { this(readers, new ReactiveAdapterRegistry()); } @@ -78,7 +78,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho * @param messageReaders readers to convert from the request body * @param adapterRegistry for adapting to other reactive types from Flux and Mono */ - protected AbstractMessageReaderArgumentResolver(List> messageReaders, + protected AbstractMessageReaderArgumentResolver(List> messageReaders, ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); @@ -94,7 +94,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho /** * Return the configured message converters. */ - public List> getMessageReaders() { + public List> getMessageReaders() { return this.messageReaders; } @@ -113,7 +113,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho mediaType = MediaType.APPLICATION_OCTET_STREAM; } - for (ServerHttpMessageReader reader : getMessageReaders()) { + for (HttpMessageReader reader : getMessageReaders()) { if (reader.canRead(elementType, mediaType)) { Map readHints = Collections.emptyMap(); if (adapter != null && adapter.isMultiValue()) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java index ba3a7ee588..dd332aeb6f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java @@ -27,7 +27,7 @@ import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.http.MediaType; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; @@ -38,24 +38,24 @@ import org.springframework.web.server.ServerWebExchange; /** * Abstract base class for result handlers that handle return values by writing - * to the response with {@link ServerHttpMessageWriter}. + * to the response with {@link HttpMessageWriter}. * * @author Rossen Stoyanchev * @since 5.0 */ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHandlerSupport { - private final List> messageWriters; + private final List> messageWriters; /** - * Constructor with {@link ServerHttpMessageWriter}s and a + * Constructor with {@link HttpMessageWriter}s and a * {@code RequestedContentTypeResolver}. * * @param messageWriters for serializing Objects to the response body stream * @param contentTypeResolver for resolving the requested content type */ - protected AbstractMessageWriterResultHandler(List> messageWriters, + protected AbstractMessageWriterResultHandler(List> messageWriters, RequestedContentTypeResolver contentTypeResolver) { super(contentTypeResolver); @@ -71,7 +71,7 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa * @param adapterRegistry for adapting other reactive types (e.g. rx.Observable, * rx.Single, etc.) to Flux or Mono */ - protected AbstractMessageWriterResultHandler(List> messageWriters, + protected AbstractMessageWriterResultHandler(List> messageWriters, RequestedContentTypeResolver contentTypeResolver, ReactiveAdapterRegistry adapterRegistry) { @@ -84,7 +84,7 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa /** * Return the configured message converters. */ - public List> getMessageWriters() { + public List> getMessageWriters() { return this.messageWriters; } @@ -115,7 +115,7 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa ServerHttpResponse response = exchange.getResponse(); MediaType bestMediaType = selectMediaType(exchange, () -> getProducibleMediaTypes(elementType)); if (bestMediaType != null) { - for (ServerHttpMessageWriter writer : getMessageWriters()) { + for (HttpMessageWriter writer : getMessageWriters()) { if (writer.canWrite(elementType, bestMediaType)) { return writer.write((Publisher) publisher, bodyType, elementType, bestMediaType, request, response, Collections.emptyMap()); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java index 7fd0576083..d4e36e8c29 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java @@ -24,7 +24,7 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.http.HttpEntity; import org.springframework.http.RequestEntity; -import org.springframework.http.codec.ServerHttpMessageReader; +import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; @@ -40,7 +40,7 @@ import org.springframework.web.server.ServerWebExchange; */ public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentResolver { - public HttpEntityArgumentResolver(List> readers, + public HttpEntityArgumentResolver(List> readers, ReactiveAdapterRegistry registry) { super(readers, registry); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java index 56ce5977d6..90b4c77010 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java @@ -22,7 +22,7 @@ import reactor.core.publisher.Mono; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.http.codec.ServerHttpMessageReader; +import org.springframework.http.codec.HttpMessageReader; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; @@ -45,7 +45,7 @@ import org.springframework.web.server.ServerWebInputException; */ public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentResolver { - public RequestBodyArgumentResolver(List> readers, + public RequestBodyArgumentResolver(List> readers, ReactiveAdapterRegistry registry) { super(readers, registry); 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 dbed19a45c..bed81fff75 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 @@ -42,9 +42,11 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.codec.ByteArrayDecoder; import org.springframework.core.codec.ByteBufferDecoder; import org.springframework.core.codec.DataBufferDecoder; +import org.springframework.core.codec.ResourceDecoder; import org.springframework.core.codec.StringDecoder; import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageReader; +import org.springframework.http.codec.ServerCodecConfigurer; +import org.springframework.http.codec.HttpMessageReader; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.web.bind.annotation.InitBinder; @@ -76,7 +78,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application private static final Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class); - private final List> messageReaders = new ArrayList<>(10); + private final List> messageReaders = new ArrayList<>(10); private WebBindingInitializer webBindingInitializer; @@ -114,26 +116,30 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application public RequestMappingHandlerAdapter() { - // TODO: improve with better (shared) defaults this.messageReaders.add(new DecoderHttpMessageReader<>(new ByteArrayDecoder())); this.messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder())); this.messageReaders.add(new DecoderHttpMessageReader<>(new DataBufferDecoder())); + this.messageReaders.add(new DecoderHttpMessageReader<>(new ResourceDecoder())); this.messageReaders.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true))); } /** - * Configure message readers to de-serialize the request body with. + * Configure HTTP message readers to de-serialize the request body with. + *

By default only basic data types such as bytes and text are registered. + * Consider using {@link ServerCodecConfigurer} to configure a richer list + * including JSON encoding . + * @see ServerCodecConfigurer */ - public void setMessageReaders(List> messageReaders) { + public void setMessageReaders(List> messageReaders) { this.messageReaders.clear(); this.messageReaders.addAll(messageReaders); } /** - * Return the configured message readers. + * Return the configured HTTP message readers. */ - public List> getMessageReaders() { + public List> getMessageReaders() { return this.messageReaders; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java index cc9a6d5c65..d0ccd3c65b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java @@ -23,7 +23,7 @@ import reactor.core.publisher.Mono; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.HttpMessageWriter; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.HandlerResultHandler; @@ -34,7 +34,7 @@ import org.springframework.web.server.ServerWebExchange; /** * {@code HandlerResultHandler} that handles return values from methods annotated * with {@code @ResponseBody} writing to the body of the request or response with - * an {@link ServerHttpMessageWriter}. + * an {@link HttpMessageWriter}. * *

By default the order for this result handler is set to 100. As it detects * the presence of {@code @ResponseBody} it should be ordered after result @@ -56,7 +56,7 @@ public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandle * @param writers writers for serializing to the response body * @param resolver to determine the requested content type */ - public ResponseBodyResultHandler(List> writers, + public ResponseBodyResultHandler(List> writers, RequestedContentTypeResolver resolver) { this(writers, resolver, new ReactiveAdapterRegistry()); @@ -68,7 +68,7 @@ public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandle * @param resolver to determine the requested content type * @param registry for adaptation to reactive types */ - public ResponseBodyResultHandler(List> writers, + public ResponseBodyResultHandler(List> writers, RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) { super(writers, resolver, registry); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java index 30bef89cc4..6470f5d1d6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java @@ -30,7 +30,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.HttpMessageWriter; import org.springframework.util.Assert; import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.HandlerResultHandler; @@ -57,7 +57,7 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand * @param writers writers for serializing to the response body * @param resolver to determine the requested content type */ - public ResponseEntityResultHandler(List> writers, + public ResponseEntityResultHandler(List> writers, RequestedContentTypeResolver resolver) { this(writers, resolver, new ReactiveAdapterRegistry()); @@ -69,7 +69,7 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand * @param resolver to determine the requested content type * @param registry for adaptation to reactive types */ - public ResponseEntityResultHandler(List> writers, + public ResponseEntityResultHandler(List> writers, RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) { super(writers, resolver, registry); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java index dc1fbc1494..403ffd2c15 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java @@ -30,8 +30,8 @@ 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.ServerHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; @@ -58,10 +58,10 @@ public class DelegatingWebFluxConfigurationTests { private WebFluxConfigurer webFluxConfigurer; @Captor - private ArgumentCaptor>> readers; + private ArgumentCaptor codecsConfigurer; @Captor - private ArgumentCaptor>> writers; + private ArgumentCaptor>> writers; @Captor private ArgumentCaptor formatterRegistry; @@ -96,15 +96,14 @@ public class DelegatingWebFluxConfigurationTests { ConversionService initializerConversionService = initializer.getConversionService(); assertTrue(initializer.getValidator() instanceof LocalValidatorFactoryBean); - verify(webFluxConfigurer).configureMessageReaders(readers.capture()); - verify(webFluxConfigurer).extendMessageReaders(readers.capture()); + verify(webFluxConfigurer).configureHttpMessageCodecs(codecsConfigurer.capture()); verify(webFluxConfigurer).getValidator(); verify(webFluxConfigurer).getMessageCodesResolver(); verify(webFluxConfigurer).addFormatters(formatterRegistry.capture()); verify(webFluxConfigurer).addArgumentResolvers(any()); assertSame(formatterRegistry.getValue(), initializerConversionService); - assertEquals(7, readers.getValue().size()); + assertEquals(8, codecsConfigurer.getValue().getReaders().size()); } @Test @@ -126,8 +125,7 @@ public class DelegatingWebFluxConfigurationTests { delegatingConfig.setConfigurers(Collections.singletonList(webFluxConfigurer)); delegatingConfig.responseBodyResultHandler(); - verify(webFluxConfigurer).configureMessageWriters(writers.capture()); - verify(webFluxConfigurer).extendMessageWriters(writers.capture()); + verify(webFluxConfigurer).configureHttpMessageCodecs(codecsConfigurer.capture()); verify(webFluxConfigurer).configureContentTypeResolver(any(RequestedContentTypeResolverBuilder.class)); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java index 99919d89e3..715f569720 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java @@ -34,10 +34,9 @@ import org.springframework.core.codec.StringDecoder; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; -import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.EncoderHttpMessageWriter; -import org.springframework.http.codec.ServerHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlDecoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; @@ -127,8 +126,8 @@ public class WebFluxConfigurationSupportTests { RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class); assertNotNull(adapter); - List> readers = adapter.getMessageReaders(); - assertEquals(7, readers.size()); + List> readers = adapter.getMessageReaders(); + assertEquals(8, readers.size()); assertHasMessageReader(readers, byte[].class, APPLICATION_OCTET_STREAM); assertHasMessageReader(readers, ByteBuffer.class, APPLICATION_OCTET_STREAM); @@ -160,7 +159,7 @@ public class WebFluxConfigurationSupportTests { RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class); assertNotNull(adapter); - List> messageReaders = adapter.getMessageReaders(); + List> messageReaders = adapter.getMessageReaders(); assertEquals(2, messageReaders.size()); assertHasMessageReader(messageReaders, String.class, TEXT_PLAIN); @@ -177,7 +176,7 @@ public class WebFluxConfigurationSupportTests { assertEquals(0, handler.getOrder()); - List> writers = handler.getMessageWriters(); + List> writers = handler.getMessageWriters(); assertEquals(9, writers.size()); assertHasMessageWriter(writers, byte[].class, APPLICATION_OCTET_STREAM); @@ -203,7 +202,7 @@ public class WebFluxConfigurationSupportTests { assertEquals(100, handler.getOrder()); - List> writers = handler.getMessageWriters(); + List> writers = handler.getMessageWriters(); assertEquals(9, writers.size()); assertHasMessageWriter(writers, byte[].class, APPLICATION_OCTET_STREAM); @@ -259,12 +258,12 @@ public class WebFluxConfigurationSupportTests { } - private void assertHasMessageReader(List> readers, Class clazz, MediaType mediaType) { + private void assertHasMessageReader(List> readers, Class clazz, MediaType mediaType) { ResolvableType type = ResolvableType.forClass(clazz); assertTrue(readers.stream().anyMatch(c -> mediaType == null || c.canRead(type, mediaType))); } - private void assertHasMessageWriter(List> writers, Class clazz, MediaType mediaType) { + private void assertHasMessageWriter(List> writers, Class clazz, MediaType mediaType) { ResolvableType type = ResolvableType.forClass(clazz); assertTrue(writers.stream().anyMatch(c -> mediaType == null || c.canWrite(type, mediaType))); } @@ -297,23 +296,12 @@ public class WebFluxConfigurationSupportTests { static class CustomMessageConverterConfig extends WebFluxConfigurationSupport { @Override - protected void configureMessageReaders(List> messageReaders) { - messageReaders.add(new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly(true))); - } - - @Override - protected void configureMessageWriters(List> messageWriters) { - messageWriters.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly())); - } - - @Override - protected void extendMessageReaders(List> messageReaders) { - messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder())); - } - - @Override - protected void extendMessageWriters(List> messageWriters) { - messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder())); + protected void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { + configurer.registerDefaults(false); + configurer.customCodec().decoder(StringDecoder.textPlainOnly(true)); + configurer.customCodec().decoder(new Jaxb2XmlDecoder()); + configurer.customCodec().encoder(CharSequenceEncoder.textPlainOnly()); + configurer.customCodec().encoder(new Jaxb2XmlEncoder()); } } 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 911436c0ec..7563397ad8 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 @@ -120,16 +120,19 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr @Bean public HandlerMapping handlerMapping(RouterFunction routerFunction, ApplicationContext applicationContext) { + return RouterFunctions.toHandlerMapping(routerFunction, new HandlerStrategies() { @Override public Supplier>> messageReaders() { - return () -> getMessageReaders().stream().map(reader -> (HttpMessageReader) reader); + return () -> getMessageCodecsConfigurer().getReaders().stream() + .map(reader -> (HttpMessageReader) reader); } @Override public Supplier>> messageWriters() { - return () -> getMessageWriters().stream().map(writer -> (HttpMessageWriter) writer); + return () -> getMessageCodecsConfigurer().getWriters().stream() + .map(writer -> (HttpMessageWriter) writer); } @Override diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java index 05bcc3d4c1..8b687439ae 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java @@ -39,7 +39,7 @@ import org.springframework.core.codec.StringDecoder; import org.springframework.http.HttpEntity; import org.springframework.http.RequestEntity; import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageReader; +import org.springframework.http.codec.HttpMessageReader; import org.springframework.mock.http.server.reactive.test.MockServerWebExchange; import org.springframework.util.ObjectUtils; import org.springframework.web.method.ResolvableMethod; @@ -73,7 +73,7 @@ public class HttpEntityArgumentResolverTests { private HttpEntityArgumentResolver createResolver() { - List> readers = new ArrayList<>(); + List> readers = new ArrayList<>(); readers.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true))); return new HttpEntityArgumentResolver(readers, new ReactiveAdapterRegistry()); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java index 7cc280bcc4..46e451e9b4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java @@ -43,7 +43,7 @@ import org.springframework.core.ResolvableType; import org.springframework.core.codec.Decoder; import org.springframework.http.MediaType; import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageReader; +import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -302,7 +302,7 @@ public class MessageReaderArgumentResolverTests { @SuppressWarnings("Convert2MethodRef") private AbstractMessageReaderArgumentResolver resolver(Decoder... decoders) { - List> readers = new ArrayList<>(); + List> readers = new ArrayList<>(); Arrays.asList(decoders).forEach(decoder -> readers.add(new DecoderHttpMessageReader<>(decoder))); return new AbstractMessageReaderArgumentResolver(readers) { @Override diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java index 5856be3f36..2b89311ae9 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java @@ -43,7 +43,7 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.ResourceHttpMessageWriter; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; @@ -71,8 +71,8 @@ public class MessageWriterResultHandlerTests { private final MockServerWebExchange exchange = MockServerHttpRequest.get("/path").toExchange(); - private AbstractMessageWriterResultHandler initResultHandler(ServerHttpMessageWriter... writers) { - List> writerList; + private AbstractMessageWriterResultHandler initResultHandler(HttpMessageWriter... writers) { + List> writerList; if (ObjectUtils.isEmpty(writers)) { writerList = new ArrayList<>(); writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); @@ -141,7 +141,7 @@ public class MessageWriterResultHandlerTests { ByteArrayOutputStream body = new ByteArrayOutputStream(); MethodParameter type = on(TestController.class).resolveReturnType(OutputStream.class); - ServerHttpMessageWriter writer = new EncoderHttpMessageWriter<>(new ByteBufferEncoder()); + HttpMessageWriter writer = new EncoderHttpMessageWriter<>(new ByteBufferEncoder()); Mono mono = initResultHandler(writer).writeBody(body, type, this.exchange); StepVerifier.create(mono).expectError(IllegalStateException.class).verify(); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java index 880e64f8e5..f9e993bac4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java @@ -35,7 +35,7 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.codec.StringDecoder; import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.ServerHttpMessageReader; +import org.springframework.http.codec.HttpMessageReader; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.method.ResolvableMethod; @@ -66,7 +66,7 @@ public class RequestBodyArgumentResolverTests { @Before public void setup() { - List> readers = new ArrayList<>(); + List> readers = new ArrayList<>(); readers.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true))); this.resolver = new RequestBodyArgumentResolver(readers, new ReactiveAdapterRegistry()); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java index 8af0ce73aa..c8bb97cba8 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java @@ -30,7 +30,7 @@ import org.springframework.core.codec.ByteBufferEncoder; import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.ResourceHttpMessageWriter; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.stereotype.Controller; @@ -64,7 +64,7 @@ public class ResponseBodyResultHandlerTests { @Before public void setup() throws Exception { - List> writerList = new ArrayList<>(5); + List> writerList = new ArrayList<>(5); writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); writerList.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes())); writerList.add(new ResourceHttpMessageWriter()); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java index dcb4cecb20..b133379379 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java @@ -44,7 +44,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.ResourceHttpMessageWriter; -import org.springframework.http.codec.ServerHttpMessageWriter; +import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.mock.http.server.reactive.test.MockServerWebExchange; @@ -83,8 +83,8 @@ public class ResponseEntityResultHandlerTests { this.resultHandler = createHandler(); } - private ResponseEntityResultHandler createHandler(ServerHttpMessageWriter... writers) { - List> writerList; + private ResponseEntityResultHandler createHandler(HttpMessageWriter... writers) { + List> writerList; if (ObjectUtils.isEmpty(writers)) { writerList = new ArrayList<>(); writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));