Introduce ServerCodecConfigurer

Issue: SPR-15247
This commit is contained in:
Rossen Stoyanchev 2017-03-23 22:57:59 -04:00
parent e644c557e7
commit 4a7218f54f
13 changed files with 731 additions and 338 deletions

View File

@ -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.ServerCodecConfigurer;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator;
@ -85,14 +85,8 @@ class DefaultControllerSpec extends AbstractMockServerSpec<WebTestClient.Control
}
@Override
public DefaultControllerSpec messageReaders(Consumer<List<ServerHttpMessageReader<?>>> consumer) {
this.configurer.readersConsumer = consumer;
return this;
}
@Override
public DefaultControllerSpec messageWriters(Consumer<List<ServerHttpMessageWriter<?>>> consumer) {
this.configurer.writersConsumer = consumer;
public DefaultControllerSpec httpMessageCodecs(Consumer<ServerCodecConfigurer> consumer) {
this.configurer.messageCodecsConsumer = consumer;
return this;
}
@ -145,7 +139,7 @@ class DefaultControllerSpec extends AbstractMockServerSpec<WebTestClient.Control
private Consumer<PathMatchConfigurer> pathMatchConsumer;
private Consumer<List<ServerHttpMessageReader<?>>> readersConsumer;
private Consumer<ServerCodecConfigurer> messageCodecsConsumer;
private Consumer<List<ServerHttpMessageWriter<?>>> writersConsumer;
@ -178,16 +172,9 @@ class DefaultControllerSpec extends AbstractMockServerSpec<WebTestClient.Control
}
@Override
public void extendMessageReaders(List<ServerHttpMessageReader<?>> readers) {
if (this.readersConsumer != null) {
this.readersConsumer.accept(readers);
}
}
@Override
public void extendMessageWriters(List<ServerHttpMessageWriter<?>> writers) {
if (this.writersConsumer != null) {
this.writersConsumer.accept(writers);
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
if (this.messageCodecsConsumer != null) {
this.messageCodecsConsumer.accept(configurer);
}
}

View File

@ -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<PathMatchConfigurer> 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<List<ServerHttpMessageReader<?>>> readers);
/**
* Modify or extend the list of built-in message writers.
* @see WebFluxConfigurer#configureMessageWriters
*/
ControllerSpec messageWriters(Consumer<List<ServerHttpMessageWriter<?>>> writers);
ControllerSpec httpMessageCodecs(Consumer<ServerCodecConfigurer> configurer);
/**
* Register formatters and converters to use for type conversion.

View File

@ -0,0 +1,356 @@
/*
* 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
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.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.ClassUtils;
/**
* Helps to configure a list of server-side HTTP message readers and writers
* with support for built-in defaults and options to register additional custom
* readers and writers via {@link #customCodec()}.
*
* <p>The 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.
* <p>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<ServerHttpMessageReader<?>> getReaders() {
// Built-in, concrete Java type readers
List<ServerHttpMessageReader<?>> 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<ServerHttpMessageWriter<?>> getWriters() {
// Built-in, concrete Java type readers
List<ServerHttpMessageWriter<?>> 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<Class<?>, ServerHttpMessageReader<?>> readers = new HashMap<>();
private final Map<Class<?>, ServerHttpMessageWriter<?>> 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.
* <p>By default the {@link #jackson2Encoder} override is used for SSE.
* @param encoder the encoder to use
*/
public void sse(Encoder<?> encoder) {
ServerHttpMessageWriter<?> 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 <T, D extends Decoder<T>> void addReaderTo(List<ServerHttpMessageReader<?>> result,
Class<D> key, Supplier<D> fallback) {
addReaderTo(result, () -> findReader(key, fallback));
}
private void addReaderTo(List<ServerHttpMessageReader<?>> result,
Supplier<ServerHttpMessageReader<?>> reader) {
if (!this.suppressed) {
result.add(reader.get());
}
}
private <T, D extends Decoder<T>> DecoderHttpMessageReader<?> findReader(
Class<D> key, Supplier<D> fallback) {
DecoderHttpMessageReader<?> reader = (DecoderHttpMessageReader<?>) this.readers.get(key);
return reader != null ? reader : new DecoderHttpMessageReader<>(fallback.get());
}
private <T, E extends Encoder<T>> void addWriterTo(List<ServerHttpMessageWriter<?>> result,
Class<E> key, Supplier<E> fallback) {
addWriterTo(result, () -> findWriter(key, fallback));
}
private void addWriterTo(List<ServerHttpMessageWriter<?>> result,
Supplier<ServerHttpMessageWriter<?>> writer) {
if (!this.suppressed) {
result.add(writer.get());
}
}
private <T, E extends Encoder<T>> EncoderHttpMessageWriter<?> findWriter(
Class<E> key, Supplier<E> fallback) {
EncoderHttpMessageWriter<?> writer = (EncoderHttpMessageWriter<?>) this.writers.get(key);
return writer != null ? writer : new EncoderHttpMessageWriter<>(fallback.get());
}
private void addStringReaderTextOnlyTo(List<ServerHttpMessageReader<?>> result) {
addReaderTo(result, () -> new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly(true)));
}
private void addStringReaderTo(List<ServerHttpMessageReader<?>> result) {
addReaderTo(result, () -> new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
}
private void addStringWriterTextPlainOnlyTo(List<ServerHttpMessageWriter<?>> result) {
addWriterTo(result, () -> new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()));
}
private void addStringWriterTo(List<ServerHttpMessageWriter<?>> result) {
addWriterTo(result, () -> new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()));
}
private void addSseWriterTo(List<ServerHttpMessageWriter<?>> result) {
addWriterTo(result, () -> {
ServerHttpMessageWriter<?> 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<ServerHttpMessageReader<?>> typedReaders = new ArrayList<>();
private final List<ServerHttpMessageWriter<?>> typedWriters = new ArrayList<>();
private final List<ServerHttpMessageReader<?>> objectReaders = new ArrayList<>();
private final List<ServerHttpMessageWriter<?>> 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 ServerHttpMessageReader}. For readers of type
* {@link DecoderHttpMessageReader} consider using the shortcut
* {@link #decoder(Decoder)} instead.
*/
public void reader(ServerHttpMessageReader<?> reader) {
boolean canReadToObject = reader.canRead(ResolvableType.forClass(Object.class), null);
(canReadToObject ? this.objectReaders : this.typedReaders).add(reader);
}
/**
* Add a custom {@link ServerHttpMessageWriter}. For readers of type
* {@link EncoderHttpMessageWriter} consider using the shortcut
* {@link #encoder(Encoder)} instead.
*/
public void writer(ServerHttpMessageWriter<?> 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<ServerHttpMessageReader<?>> result) {
result.addAll(this.typedReaders);
}
private void addObjectReadersTo(List<ServerHttpMessageReader<?>> result) {
result.addAll(this.objectReaders);
}
private void addTypedWritersTo(List<ServerHttpMessageWriter<?>> result) {
result.addAll(this.typedWriters);
}
private void addObjectWritersTo(List<ServerHttpMessageWriter<?>> result) {
result.addAll(this.objectWriters);
}
}
}

View File

@ -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<ServerHttpMessageReader<?>> 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<ServerHttpMessageWriter<?>> 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);
ServerHttpMessageReader<?> customReader1 = mock(ServerHttpMessageReader.class);
ServerHttpMessageReader<?> customReader2 = mock(ServerHttpMessageReader.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<ServerHttpMessageReader<?>> 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);
ServerHttpMessageWriter<?> customWriter1 = mock(ServerHttpMessageWriter.class);
ServerHttpMessageWriter<?> customWriter2 = mock(ServerHttpMessageWriter.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<ServerHttpMessageWriter<?>> 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);
ServerHttpMessageReader<?> customReader1 = mock(ServerHttpMessageReader.class);
ServerHttpMessageReader<?> customReader2 = mock(ServerHttpMessageReader.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<ServerHttpMessageReader<?>> 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);
ServerHttpMessageWriter<?> customWriter1 = mock(ServerHttpMessageWriter.class);
ServerHttpMessageWriter<?> customWriter2 = mock(ServerHttpMessageWriter.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<ServerHttpMessageWriter<?>> 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<ServerHttpMessageReader<?>> readers) {
HttpMessageReader<?> reader = readers.get(this.index.getAndIncrement());
assertEquals(DecoderHttpMessageReader.class, reader.getClass());
return ((DecoderHttpMessageReader) reader).getDecoder();
}
private Encoder<?> getNextEncoder(List<ServerHttpMessageWriter<?>> 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<ServerHttpMessageWriter<?>> writers) {
ServerHttpMessageWriter<?> writer = writers.get(this.index.getAndIncrement());
assertEquals(ServerSentEventHttpMessageWriter.class, writer.getClass());
Encoder<?> encoder = ((ServerSentEventHttpMessageWriter) writer).getEncoder();
assertNotNull(encoder);
assertEquals(Jackson2JsonEncoder.class, encoder.getClass());
}
}

View File

@ -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<ServerHttpMessageReader<?>> messageReaders) {
this.configurers.configureMessageReaders(messageReaders);
}
@Override
protected void extendMessageReaders(List<ServerHttpMessageReader<?>> 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<ServerHttpMessageWriter<?>> messageWriters) {
this.configurers.configureMessageWriters(messageWriters);
}
@Override
protected void extendMessageWriters(List<ServerHttpMessageWriter<?>> messageWriters) {
this.configurers.extendMessageWriters(messageWriters);
}
@Override
protected void configureViewResolvers(ViewResolverRegistry registry) {
this.configurers.configureViewResolvers(registry);

View File

@ -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<ServerHttpMessageReader<?>> messageReaders;
private List<ServerHttpMessageWriter<?>> 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.
* <p>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.
* <p>Use {@link #configureHttpMessageCodecs(ServerCodecConfigurer)} to
* configure the readers and writers.
*/
protected final List<ServerHttpMessageReader<?>> 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.
* <p>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<ServerHttpMessageReader<?>> 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<ServerHttpMessageReader<?>> 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<ServerHttpMessageReader<?>> 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.
* <p>Use {@link #configureMessageWriters(List)} to configure the list or
* {@link #extendMessageWriters(List)} to add in addition to the default ones.
*/
protected final List<ServerHttpMessageWriter<?>> 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.
* <p>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<ServerHttpMessageWriter<?>> messageWriters) {
}
/**
* Adds default converters that sub-classes can call from
* {@link #configureMessageWriters(List)}.
*/
protected final void addDefaultHttpMessageWriters(List<ServerHttpMessageWriter<?>> 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<ServerHttpMessageWriter<?>> messageWriters) {
return new ResponseBodyResultHandler(getMessageCodecsConfigurer().getWriters(),
webFluxContentTypeResolver(), webFluxAdapterRegistry());
}
@Bean

View File

@ -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}.
* <p>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<ServerHttpMessageReader<?>> 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<ServerHttpMessageReader<?>> 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)}.
* <p>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<ServerHttpMessageWriter<?>> 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<ServerHttpMessageWriter<?>> writers) {
}
/**
* Configure view resolution for processing the return values of controller
* methods that rely on resolving a

View File

@ -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<HandlerMethodArgumentResolver> resolvers) {
this.delegates.stream().forEach(delegate -> delegate.addArgumentResolvers(resolvers));
this.delegates.forEach(delegate -> delegate.addArgumentResolvers(resolvers));
}
@Override
public void configureMessageReaders(List<ServerHttpMessageReader<?>> readers) {
this.delegates.stream().forEach(delegate -> delegate.configureMessageReaders(readers));
}
@Override
public void extendMessageReaders(List<ServerHttpMessageReader<?>> 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<ServerHttpMessageWriter<?>> writers) {
this.delegates.stream().forEach(delegate -> delegate.configureMessageWriters(writers));
}
@Override
public void extendMessageWriters(List<ServerHttpMessageWriter<?>> 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 <T> Optional<T> createSingleBean(Function<WebFluxConfigurer, Optional<T>> factory,

View File

@ -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<HttpMessageReader<?>> 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);

View File

@ -42,8 +42,10 @@ 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.ServerCodecConfigurer;
import org.springframework.http.codec.ServerHttpMessageReader;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
@ -114,16 +116,20 @@ 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.
* <p>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<ServerHttpMessageReader<?>> messageReaders) {
this.messageReaders.clear();
@ -131,7 +137,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
}
/**
* Return the configured message readers.
* Return the configured HTTP message readers.
*/
public List<ServerHttpMessageReader<?>> getMessageReaders() {
return this.messageReaders;

View File

@ -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.ServerCodecConfigurer;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
@ -58,7 +58,7 @@ public class DelegatingWebFluxConfigurationTests {
private WebFluxConfigurer webFluxConfigurer;
@Captor
private ArgumentCaptor<List<ServerHttpMessageReader<?>>> readers;
private ArgumentCaptor<ServerCodecConfigurer> codecsConfigurer;
@Captor
private ArgumentCaptor<List<ServerHttpMessageWriter<?>>> writers;
@ -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));
}

View File

@ -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.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
@ -128,7 +127,7 @@ public class WebFluxConfigurationSupportTests {
assertNotNull(adapter);
List<ServerHttpMessageReader<?>> readers = adapter.getMessageReaders();
assertEquals(7, readers.size());
assertEquals(8, readers.size());
assertHasMessageReader(readers, byte[].class, APPLICATION_OCTET_STREAM);
assertHasMessageReader(readers, ByteBuffer.class, APPLICATION_OCTET_STREAM);
@ -297,23 +296,12 @@ public class WebFluxConfigurationSupportTests {
static class CustomMessageConverterConfig extends WebFluxConfigurationSupport {
@Override
protected void configureMessageReaders(List<ServerHttpMessageReader<?>> messageReaders) {
messageReaders.add(new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly(true)));
}
@Override
protected void configureMessageWriters(List<ServerHttpMessageWriter<?>> messageWriters) {
messageWriters.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()));
}
@Override
protected void extendMessageReaders(List<ServerHttpMessageReader<?>> messageReaders) {
messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
}
@Override
protected void extendMessageWriters(List<ServerHttpMessageWriter<?>> 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());
}
}

View File

@ -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<Stream<HttpMessageReader<?>>> messageReaders() {
return () -> getMessageReaders().stream().map(reader -> (HttpMessageReader<?>) reader);
return () -> getMessageCodecsConfigurer().getReaders().stream()
.map(reader -> (HttpMessageReader<?>) reader);
}
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return () -> getMessageWriters().stream().map(writer -> (HttpMessageWriter<?>) writer);
return () -> getMessageCodecsConfigurer().getWriters().stream()
.map(writer -> (HttpMessageWriter<?>) writer);
}
@Override