Use refactored CodecConfigurer

This commit changes the `HandlerStrategies` and `ExchangeStrategies`
builders to use the `CodecConfigurer` for configuring Decoder|Encoder
and HttpMessage[Reader|Writer]. Other classes that use `CodecConfigurer`
have also been changed to reflect the refactoring to interfaces.

This commit also removes the ExchangeStrategies methods that take an
application context, as it was too naive approach to simply look up
every message reader and writer in the context.

Issue: SPR-15415, SPR-15435
This commit is contained in:
Arjen Poutsma 2017-04-13 11:01:46 +02:00
parent 908d16904f
commit dc279d839b
9 changed files with 64 additions and 314 deletions

View File

@ -70,6 +70,13 @@ import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
*/
public class WebFluxConfigurationSupport implements ApplicationContextAware {
static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
WebFluxConfigurationSupport.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
WebFluxConfigurationSupport.class.getClassLoader());
private Map<String, CorsConfiguration> corsConfigurations;
private PathMatchConfigurer pathMatchConfigurer;
@ -148,7 +155,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
*/
protected Map<String, MediaType> getDefaultMediaTypeMappings() {
Map<String, MediaType> map = new HashMap<>();
if (ServerCodecConfigurer.jackson2Present) {
if (jackson2Present) {
map.put("json", MediaType.APPLICATION_JSON);
}
return map;
@ -266,7 +273,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
*/
protected final ServerCodecConfigurer getMessageCodecsConfigurer() {
if (this.messageCodecsConfigurer == null) {
this.messageCodecsConfigurer = new ServerCodecConfigurer();
this.messageCodecsConfigurer = ServerCodecConfigurer.create();
configureHttpMessageCodecs(this.getMessageCodecsConfigurer());
}
return this.messageCodecsConfigurer;

View File

@ -19,15 +19,11 @@ package org.springframework.web.reactive.function.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.context.ApplicationContext;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.util.Assert;
@ -40,51 +36,37 @@ import org.springframework.util.Assert;
*/
class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Builder {
private final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
private final ClientCodecConfigurer codecConfigurer = ClientCodecConfigurer.create();
private final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
public DefaultExchangeStrategiesBuilder() {
this.codecConfigurer.registerDefaults(false);
}
public void defaultConfiguration() {
ClientCodecConfigurer configurer = new ClientCodecConfigurer();
configurer.getReaders().forEach(this::messageReader);
configurer.getWriters().forEach(this::messageWriter);
}
public void applicationContext(ApplicationContext applicationContext) {
applicationContext.getBeansOfType(HttpMessageReader.class).values().forEach(this::messageReader);
applicationContext.getBeansOfType(HttpMessageWriter.class).values().forEach(this::messageWriter);
this.codecConfigurer.registerDefaults(true);
}
@Override
public ExchangeStrategies.Builder messageReader(HttpMessageReader<?> messageReader) {
Assert.notNull(messageReader, "'messageReader' must not be null");
this.messageReaders.add(messageReader);
public ExchangeStrategies.Builder defaultCodecs(
Consumer<ClientCodecConfigurer.ClientDefaultCodecsConfigurer> consumer) {
Assert.notNull(consumer, "'consumer' must not be null");
consumer.accept(this.codecConfigurer.defaultCodecs());
return this;
}
@Override
public ExchangeStrategies.Builder decoder(Decoder<?> decoder) {
Assert.notNull(decoder, "'decoder' must not be null");
return messageReader(new DecoderHttpMessageReader<>(decoder));
}
@Override
public ExchangeStrategies.Builder messageWriter(HttpMessageWriter<?> messageWriter) {
Assert.notNull(messageWriter, "'messageWriter' must not be null");
this.messageWriters.add(messageWriter);
public ExchangeStrategies.Builder customCodecs(
Consumer<ClientCodecConfigurer.CustomCodecsConfigurer> consumer) {
Assert.notNull(consumer, "'consumer' must not be null");
consumer.accept(this.codecConfigurer.customCodecs());
return this;
}
@Override
public ExchangeStrategies.Builder encoder(Encoder<?> encoder) {
Assert.notNull(encoder, "'encoder' must not be null");
return messageWriter(new EncoderHttpMessageWriter<>(encoder));
}
@Override
public ExchangeStrategies build() {
return new DefaultExchangeStrategies(this.messageReaders, this.messageWriters);
return new DefaultExchangeStrategies(this.codecConfigurer.getReaders(),
this.codecConfigurer.getWriters());
}

View File

@ -16,15 +16,13 @@
package org.springframework.web.reactive.function.client;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.context.ApplicationContext;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.util.Assert;
/**
* Defines the strategies for invoking {@link ExchangeFunction}s. An instance of
@ -66,48 +64,6 @@ public interface ExchangeStrategies {
return builder().build();
}
/**
* Return a new {@code ExchangeStrategies} based on the given
* {@linkplain ApplicationContext application context}.
* The returned supplier will search for all {@link HttpMessageReader}, and
* {@link HttpMessageWriter} instances in the given application context and return
* them for {@link #messageReaders()}, and {@link #messageWriters()} respectively.
* @param applicationContext the application context to base the strategies on
* @return the new {@code ExchangeStrategies}
*/
static ExchangeStrategies of(ApplicationContext applicationContext) {
return builder(applicationContext).build();
}
/**
* Return a new {@code ExchangeStrategies} described by the given supplier functions.
* All provided supplier function parameters can be {@code null} to indicate an empty
* stream is to be returned.
* @param messageReaders the supplier function for {@link HttpMessageReader} instances
* (can be {@code null})
* @param messageWriters the supplier function for {@link HttpMessageWriter} instances
* (can be {@code null})
* @return the new {@code ExchangeStrategies}
*/
static ExchangeStrategies of(Supplier<Stream<HttpMessageReader<?>>> messageReaders,
Supplier<Stream<HttpMessageWriter<?>>> messageWriters) {
return new ExchangeStrategies() {
@Override
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
return checkForNull(messageReaders);
}
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return checkForNull(messageWriters);
}
private <T> Supplier<Stream<T>> checkForNull(Supplier<Stream<T>> supplier) {
return supplier != null ? supplier : Stream::empty;
}
};
}
// Builder methods
/**
@ -120,21 +76,6 @@ public interface ExchangeStrategies {
return builder;
}
/**
* Return a mutable builder based on the given {@linkplain ApplicationContext application context}.
* The returned builder will search for all {@link HttpMessageReader}, and
* {@link HttpMessageWriter} instances in the given application context and return them for
* {@link #messageReaders()}, and {@link #messageWriters()}.
* @param applicationContext the application context to base the strategies on
* @return the builder
*/
static Builder builder(ApplicationContext applicationContext) {
Assert.notNull(applicationContext, "ApplicationContext must not be null");
DefaultExchangeStrategiesBuilder builder = new DefaultExchangeStrategiesBuilder();
builder.applicationContext(applicationContext);
return builder;
}
/**
* Return a mutable, empty builder for a {@code ExchangeStrategies}.
* @return the builder
@ -150,34 +91,20 @@ public interface ExchangeStrategies {
interface Builder {
/**
* Add the given message reader to this builder.
* @param messageReader the message reader to add
* Customize the list of default client-side HTTP message readers and writers.
* @param consumer the consumer to customize the default codecs
* @return this builder
* @see #customCodecs(Consumer)
*/
Builder messageReader(HttpMessageReader<?> messageReader);
Builder defaultCodecs(Consumer<ClientCodecConfigurer.ClientDefaultCodecsConfigurer> consumer);
/**
* Add the given decoder to this builder. This is a convenient alternative to adding a
* {@link org.springframework.http.codec.DecoderHttpMessageReader} that wraps the given decoder.
* @param decoder the decoder to add
* Customize the list of custom client-side HTTP message readers and writers.
* @param consumer the consumer to customize the custom codecs
* @return this builder
* @see #defaultCodecs(Consumer)
*/
Builder decoder(Decoder<?> decoder);
/**
* Add the given message writer to this builder.
* @param messageWriter the message writer to add
* @return this builder
*/
Builder messageWriter(HttpMessageWriter<?> messageWriter);
/**
* Add the given encoder to this builder. This is a convenient alternative to adding a
* {@link org.springframework.http.codec.EncoderHttpMessageWriter} that wraps the given encoder.
* @param encoder the encoder to add
* @return this builder
*/
Builder encoder(Encoder<?> encoder);
Builder customCodecs(Consumer<ClientCodecConfigurer.CustomCodecsConfigurer> consumer);
/**
* Builds the {@link ExchangeStrategies}.

View File

@ -21,17 +21,15 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder;
import org.springframework.http.codec.CodecConfigurer;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.util.Assert;
import org.springframework.web.reactive.result.view.ViewResolver;
@ -49,7 +47,8 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
.map(Locale::forLanguageTag).findFirst();
private final ServerCodecConfigurer codecConfigurer = new ServerCodecConfigurer();
private final ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
private final List<ViewResolver> viewResolvers = new ArrayList<>();
private Function<ServerRequest, Optional<Locale>> localeResolver;
@ -66,51 +65,18 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
}
@Override
public HandlerStrategies.Builder serverSentEventEncoder(Encoder<?> encoder) {
Assert.notNull(encoder, "'encoder' must not be null");
this.codecConfigurer.defaultCodecs().serverSentEventEncoder(encoder);
public HandlerStrategies.Builder defaultCodecs(
Consumer<ServerCodecConfigurer.ServerDefaultCodecsConfigurer> consumer) {
Assert.notNull(consumer, "'consumer' must not be null");
consumer.accept(this.codecConfigurer.defaultCodecs());
return this;
}
@Override
public HandlerStrategies.Builder jackson2Decoder(Jackson2JsonDecoder decoder) {
Assert.notNull(decoder, "'decoder' must not be null");
this.codecConfigurer.defaultCodecs().jackson2Decoder(decoder);
return this;
}
@Override
public HandlerStrategies.Builder jackson2Encoder(Jackson2JsonEncoder encoder) {
Assert.notNull(encoder, "'encoder' must not be null");
this.codecConfigurer.defaultCodecs().jackson2Encoder(encoder);
return this;
}
@Override
public HandlerStrategies.Builder customDecoder(Decoder<?> decoder) {
Assert.notNull(decoder, "'decoder' must not be null");
this.codecConfigurer.customCodecs().decoder(decoder);
return this;
}
@Override
public HandlerStrategies.Builder customEncoder(Encoder<?> encoder) {
Assert.notNull(encoder, "'encoder' must not be null");
this.codecConfigurer.customCodecs().encoder(encoder);
return this;
}
@Override
public HandlerStrategies.Builder customMessageReader(HttpMessageReader<?> reader) {
Assert.notNull(reader, "'reader' must not be null");
this.codecConfigurer.customCodecs().reader(reader);
return this;
}
@Override
public HandlerStrategies.Builder customMessageWriter(HttpMessageWriter<?> writer) {
Assert.notNull(writer, "'writer' must not be null");
this.codecConfigurer.customCodecs().writer(writer);
public HandlerStrategies.Builder customCodecs(
Consumer<CodecConfigurer.CustomCodecsConfigurer> consumer) {
Assert.notNull(consumer, "'consumer' must not be null");
consumer.accept(this.codecConfigurer.customCodecs());
return this;
}

View File

@ -18,18 +18,14 @@ package org.springframework.web.reactive.function.server;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
/**
@ -113,60 +109,20 @@ public interface HandlerStrategies {
interface Builder {
/**
* Configure the {@code Encoder} to use for Server-Sent Events.
* <p>By default the {@link #jackson2Encoder} override is used for SSE.
* @param encoder the encoder to use
* Customize the list of default server-side HTTP message readers and writers.
* @param consumer the consumer to customize the default codecs
* @return this builder
* @see #customCodecs(Consumer)
*/
Builder serverSentEventEncoder(Encoder<?> encoder);
Builder defaultCodecs(Consumer<ServerCodecConfigurer.ServerDefaultCodecsConfigurer> consumer);
/**
* Override the default Jackson {@code Decoder}.
* @param decoder the decoder to use
* Customize the list of custom server-side HTTP message readers and writers.
* @param consumer the consumer to customize the custom codecs
* @return this builder
* @see #defaultCodecs(Consumer)
*/
Builder jackson2Decoder(Jackson2JsonDecoder decoder);
/**
* Override the default Jackson {@code Encoder} for JSON.
* @param encoder the encoder to use
* @return this builder
*/
Builder jackson2Encoder(Jackson2JsonEncoder encoder);
/**
* Add a custom {@code Decoder} internally wrapped with
* {@link DecoderHttpMessageReader}).
* @param decoder the decoder to add
* @return this builder
*/
Builder customDecoder(Decoder<?> decoder);
/**
* Add a custom {@code Encoder}, internally wrapped with
* {@link EncoderHttpMessageWriter}.
* @param encoder the encoder to add
* @return this builder
*/
Builder customEncoder(Encoder<?> encoder);
/**
* Add a custom {@link HttpMessageReader}. For readers of type
* {@link DecoderHttpMessageReader} consider using the shortcut
* {@link #customDecoder(Decoder)} instead.
* @param reader the reader to add
* @return this builder
*/
Builder customMessageReader(HttpMessageReader<?> reader);
/**
* Add a custom {@link HttpMessageWriter}. For readers of type
* {@link EncoderHttpMessageWriter} consider using the shortcut
* {@link #customEncoder(Encoder)} instead.
* @param writer the writer to add
* @return this builder
*/
Builder customMessageWriter(HttpMessageWriter<?> writer);
Builder customCodecs(Consumer<ServerCodecConfigurer.CustomCodecsConfigurer> consumer);
/**
* Add the given view resolver to this builder.

View File

@ -146,7 +146,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
public void afterPropertiesSet() throws Exception {
if (this.messageCodecConfigurer == null) {
this.messageCodecConfigurer = new ServerCodecConfigurer();
this.messageCodecConfigurer = ServerCodecConfigurer.create();
}
if (this.argumentResolverConfigurer == null) {

View File

@ -16,25 +16,9 @@
package org.springframework.web.reactive.function.client;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import static org.junit.Assert.*;
@ -51,82 +35,10 @@ public class ExchangeStrategiesTests {
}
@Test
public void ofSuppliers() {
HttpMessageReader<?> messageReader = new DummyMessageReader();
HttpMessageWriter<?> messageWriter = new DummyMessageWriter();
ExchangeStrategies strategies = ExchangeStrategies.of(
() -> Stream.of(messageReader),
() -> Stream.of(messageWriter));
assertEquals(1L, strategies.messageReaders().get().collect(Collectors.counting()).longValue());
assertEquals(Optional.of(messageReader), strategies.messageReaders().get().findFirst());
assertEquals(1L, strategies.messageWriters().get().collect(Collectors.counting()).longValue());
assertEquals(Optional.of(messageWriter), strategies.messageWriters().get().findFirst());
}
@Test
public void toConfiguration() throws Exception {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("messageWriter", DummyMessageWriter.class);
applicationContext.registerSingleton("messageReader", DummyMessageReader.class);
applicationContext.refresh();
ExchangeStrategies strategies = ExchangeStrategies.of(applicationContext);
assertTrue(strategies.messageReaders().get()
.allMatch(r -> r instanceof DummyMessageReader));
assertTrue(strategies.messageWriters().get()
.allMatch(r -> r instanceof DummyMessageWriter));
}
private static class DummyMessageWriter implements HttpMessageWriter<Object> {
@Override
public boolean canWrite(ResolvableType type, MediaType mediaType) {
return false;
}
@Override
public List<MediaType> getWritableMediaTypes() {
return Collections.emptyList();
}
@Override
public Mono<Void> write(Publisher<?> inputStream, ResolvableType type,
MediaType contentType,
ReactiveHttpOutputMessage message,
Map<String, Object> hints) {
return Mono.empty();
}
}
private static class DummyMessageReader implements HttpMessageReader<Object> {
@Override
public boolean canRead(ResolvableType type, MediaType mediaType) {
return false;
}
@Override
public List<MediaType> getReadableMediaTypes() {
return Collections.emptyList();
}
@Override
public Flux<Object> read(ResolvableType type, ReactiveHttpInputMessage message,
Map<String, Object> hints) {
return Flux.empty();
}
@Override
public Mono<Object> readMono(ResolvableType type, ReactiveHttpInputMessage message,
Map<String, Object> hints) {
return Mono.empty();
}
public void withDefaults() {
ExchangeStrategies strategies = ExchangeStrategies.withDefaults();
assertNotEquals(Optional.empty(), strategies.messageReaders().get().findFirst());
assertNotEquals(Optional.empty(), strategies.messageWriters().get().findFirst());
}
}

View File

@ -191,7 +191,7 @@ public class DefaultEntityResponseBuilderTests {
MockServerWebExchange exchange = MockServerHttpRequest.get("http://localhost").toExchange();
HandlerStrategies strategies = HandlerStrategies.empty()
.customMessageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()))
.customCodecs(configurer -> configurer.writer(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes())))
.build();
StepVerifier.create(result)

View File

@ -67,7 +67,7 @@ public class ControllerMethodResolverTests {
resolvers.addCustomResolver(new CustomArgumentResolver());
resolvers.addCustomResolver(new CustomSyncArgumentResolver());
ServerCodecConfigurer codecs = new ServerCodecConfigurer();
ServerCodecConfigurer codecs = ServerCodecConfigurer.create();
codecs.customCodecs().decoder(new ByteArrayDecoder());
codecs.customCodecs().decoder(new ByteBufferDecoder());