Use ServerCodecConfigurer in HandlerStrategies
This commit changes the `HandlerStrategies` builder to use `ServerCodecConfigurer` for configuring Decoder|Encoder and HttpMessage[Reader|Writer]. Note that the configurer is not exposed directly, but wrapped so that `HandlerStrategies` can keep on returning `this` for a proper builder pattern. This commit also removes the HandlerStrategies 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
This commit is contained in:
parent
870e1897b6
commit
ac27dbea4b
|
|
@ -25,10 +25,13 @@ import java.util.function.Function;
|
|||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
|
||||
|
|
@ -46,40 +49,68 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
|||
.map(Locale::forLanguageTag).findFirst();
|
||||
|
||||
|
||||
private final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
|
||||
|
||||
private final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
|
||||
|
||||
private final ServerCodecConfigurer codecConfigurer = new ServerCodecConfigurer();
|
||||
private final List<ViewResolver> viewResolvers = new ArrayList<>();
|
||||
|
||||
private Function<ServerRequest, Optional<Locale>> localeResolver;
|
||||
|
||||
|
||||
public void defaultConfiguration() {
|
||||
ServerCodecConfigurer configurer = new ServerCodecConfigurer();
|
||||
configurer.getReaders().forEach(this::messageReader);
|
||||
configurer.getWriters().forEach(this::messageWriter);
|
||||
localeResolver(DEFAULT_LOCALE_RESOLVER);
|
||||
|
||||
public DefaultHandlerStrategiesBuilder() {
|
||||
this.codecConfigurer.registerDefaults(false);
|
||||
}
|
||||
|
||||
public void applicationContext(ApplicationContext applicationContext) {
|
||||
applicationContext.getBeansOfType(HttpMessageReader.class).values().forEach(this::messageReader);
|
||||
applicationContext.getBeansOfType(HttpMessageWriter.class).values().forEach(this::messageWriter);
|
||||
applicationContext.getBeansOfType(ViewResolver.class).values().forEach(this::viewResolver);
|
||||
public void defaultConfiguration() {
|
||||
this.codecConfigurer.registerDefaults(true);
|
||||
localeResolver(DEFAULT_LOCALE_RESOLVER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerStrategies.Builder messageReader(HttpMessageReader<?> messageReader) {
|
||||
Assert.notNull(messageReader, "'messageReader' must not be null");
|
||||
this.messageReaders.add(messageReader);
|
||||
public HandlerStrategies.Builder serverSentEventEncoder(Encoder<?> encoder) {
|
||||
Assert.notNull(encoder, "'encoder' must not be null");
|
||||
this.codecConfigurer.defaultCodecs().serverSentEventEncoder(encoder);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerStrategies.Builder messageWriter(HttpMessageWriter<?> messageWriter) {
|
||||
Assert.notNull(messageWriter, "'messageWriter' must not be null");
|
||||
this.messageWriters.add(messageWriter);
|
||||
public HandlerStrategies.Builder jackson2Decoder(Jackson2JsonDecoder decoder) {
|
||||
Assert.notNull(decoder, "'decoder' must not be null");
|
||||
this.codecConfigurer.defaultCodecs().jackson2Decoder(decoder);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerStrategies.Builder jackson2Encoder(Jackson2JsonEncoder encoder) {
|
||||
Assert.notNull(encoder, "'encoder' must not be null");
|
||||
this.codecConfigurer.defaultCodecs().jackson2Encoder(encoder);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerStrategies.Builder customDecoder(Decoder<?> decoder) {
|
||||
Assert.notNull(decoder, "'decoder' must not be null");
|
||||
this.codecConfigurer.customCodecs().decoder(decoder);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerStrategies.Builder customEncoder(Encoder<?> encoder) {
|
||||
Assert.notNull(encoder, "'encoder' must not be null");
|
||||
this.codecConfigurer.customCodecs().encoder(encoder);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerStrategies.Builder customMessageReader(HttpMessageReader<?> reader) {
|
||||
Assert.notNull(reader, "'reader' must not be null");
|
||||
this.codecConfigurer.customCodecs().reader(reader);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerStrategies.Builder customMessageWriter(HttpMessageWriter<?> writer) {
|
||||
Assert.notNull(writer, "'writer' must not be null");
|
||||
this.codecConfigurer.customCodecs().writer(writer);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -99,8 +130,8 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
|||
|
||||
@Override
|
||||
public HandlerStrategies build() {
|
||||
return new DefaultHandlerStrategies(this.messageReaders, this.messageWriters,
|
||||
this.viewResolvers, localeResolver);
|
||||
return new DefaultHandlerStrategies(this.codecConfigurer.getReaders(),
|
||||
this.codecConfigurer.getWriters(), this.viewResolvers, this.localeResolver);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -146,8 +177,8 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Function<ServerRequest, Optional<Locale>> localeResolver() {
|
||||
return this.localeResolver;
|
||||
public Supplier<Function<ServerRequest, Optional<Locale>>> localeResolver() {
|
||||
return () -> this.localeResolver;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder {
|
|||
.orElseThrow(() -> new IllegalStateException(
|
||||
"Could not find ServerRequest in exchange attributes"));
|
||||
|
||||
return strategies.localeResolver()
|
||||
return strategies.localeResolver().get()
|
||||
.apply(request)
|
||||
.orElse(Locale.getDefault());
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,14 @@ import java.util.function.Function;
|
|||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.http.codec.DecoderHttpMessageReader;
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
|
||||
/**
|
||||
|
|
@ -69,7 +73,7 @@ public interface HandlerStrategies {
|
|||
* Supply a function that resolves the locale of a given {@link ServerRequest}.
|
||||
* @return the locale resolver
|
||||
*/
|
||||
Function<ServerRequest, Optional<Locale>> localeResolver();
|
||||
Supplier<Function<ServerRequest, Optional<Locale>>> localeResolver();
|
||||
|
||||
|
||||
// Static methods
|
||||
|
|
@ -82,20 +86,6 @@ public interface HandlerStrategies {
|
|||
return builder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@code HandlerStrategies} based on the given
|
||||
* {@linkplain ApplicationContext application context}.
|
||||
* The returned supplier will search for all {@link HttpMessageReader}, {@link HttpMessageWriter},
|
||||
* and {@link ViewResolver} instances in the given application context and return them for
|
||||
* {@link #messageReaders()}, {@link #messageWriters()}, and {@link #viewResolvers()}
|
||||
* respectively.
|
||||
* @param applicationContext the application context to base the strategies on
|
||||
* @return the new {@code HandlerStrategies}
|
||||
*/
|
||||
static HandlerStrategies of(ApplicationContext applicationContext) {
|
||||
return builder(applicationContext).build();
|
||||
}
|
||||
|
||||
// Builder methods
|
||||
|
||||
/**
|
||||
|
|
@ -108,22 +98,6 @@ public interface HandlerStrategies {
|
|||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a mutable builder based on the given {@linkplain ApplicationContext application context}.
|
||||
* The returned builder will search for all {@link HttpMessageReader}, {@link HttpMessageWriter},
|
||||
* and {@link ViewResolver} instances in the given application context and return them for
|
||||
* {@link #messageReaders()}, {@link #messageWriters()}, and {@link #viewResolvers()}
|
||||
* respectively.
|
||||
* @param applicationContext the application context to base the strategies on
|
||||
* @return the builder
|
||||
*/
|
||||
static Builder builder(ApplicationContext applicationContext) {
|
||||
Assert.notNull(applicationContext, "ApplicationContext must not be null");
|
||||
DefaultHandlerStrategiesBuilder builder = new DefaultHandlerStrategiesBuilder();
|
||||
builder.applicationContext(applicationContext);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a mutable, empty builder for a {@code HandlerStrategies}.
|
||||
* @return the builder
|
||||
|
|
@ -139,18 +113,60 @@ public interface HandlerStrategies {
|
|||
interface Builder {
|
||||
|
||||
/**
|
||||
* Add the given message reader to this builder.
|
||||
* @param messageReader the message reader to add
|
||||
* Configure the {@code Encoder} to use for Server-Sent Events.
|
||||
* <p>By default the {@link #jackson2Encoder} override is used for SSE.
|
||||
* @param encoder the encoder to use
|
||||
* @return this builder
|
||||
*/
|
||||
Builder messageReader(HttpMessageReader<?> messageReader);
|
||||
Builder serverSentEventEncoder(Encoder<?> encoder);
|
||||
|
||||
/**
|
||||
* Add the given message writer to this builder.
|
||||
* @param messageWriter the message writer to add
|
||||
* Override the default Jackson {@code Decoder}.
|
||||
* @param decoder the decoder to use
|
||||
* @return this builder
|
||||
*/
|
||||
Builder messageWriter(HttpMessageWriter<?> messageWriter);
|
||||
Builder jackson2Decoder(Jackson2JsonDecoder decoder);
|
||||
|
||||
/**
|
||||
* Override the default Jackson {@code Encoder} for JSON.
|
||||
* @param encoder the encoder to use
|
||||
* @return this builder
|
||||
*/
|
||||
Builder jackson2Encoder(Jackson2JsonEncoder encoder);
|
||||
|
||||
/**
|
||||
* Add a custom {@code Decoder} internally wrapped with
|
||||
* {@link DecoderHttpMessageReader}).
|
||||
* @param decoder the decoder to add
|
||||
* @return this builder
|
||||
*/
|
||||
Builder customDecoder(Decoder<?> decoder);
|
||||
|
||||
/**
|
||||
* Add a custom {@code Encoder}, internally wrapped with
|
||||
* {@link EncoderHttpMessageWriter}.
|
||||
* @param encoder the encoder to add
|
||||
* @return this builder
|
||||
*/
|
||||
Builder customEncoder(Encoder<?> encoder);
|
||||
|
||||
/**
|
||||
* Add a custom {@link HttpMessageReader}. For readers of type
|
||||
* {@link DecoderHttpMessageReader} consider using the shortcut
|
||||
* {@link #customDecoder(Decoder)} instead.
|
||||
* @param reader the reader to add
|
||||
* @return this builder
|
||||
*/
|
||||
Builder customMessageReader(HttpMessageReader<?> reader);
|
||||
|
||||
/**
|
||||
* Add a custom {@link HttpMessageWriter}. For readers of type
|
||||
* {@link EncoderHttpMessageWriter} consider using the shortcut
|
||||
* {@link #customEncoder(Encoder)} instead.
|
||||
* @param writer the writer to add
|
||||
* @return this builder
|
||||
*/
|
||||
Builder customMessageWriter(HttpMessageWriter<?> writer);
|
||||
|
||||
/**
|
||||
* Add the given view resolver to this builder.
|
||||
|
|
|
|||
|
|
@ -46,8 +46,7 @@ import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
|
|||
import org.springframework.web.reactive.function.BodyInserter;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
|
|
@ -192,7 +191,7 @@ public class DefaultEntityResponseBuilderTests {
|
|||
MockServerWebExchange exchange = MockServerHttpRequest.get("http://localhost").toExchange();
|
||||
|
||||
HandlerStrategies strategies = HandlerStrategies.empty()
|
||||
.messageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()))
|
||||
.customMessageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()))
|
||||
.build();
|
||||
|
||||
StepVerifier.create(result)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ import org.springframework.web.reactive.function.server.support.ServerResponseRe
|
|||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromPublisher;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
|
|
@ -141,8 +141,8 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
|
|||
}
|
||||
|
||||
@Override
|
||||
public Function<ServerRequest, Optional<Locale>> localeResolver() {
|
||||
return DefaultHandlerStrategiesBuilder.DEFAULT_LOCALE_RESOLVER;
|
||||
public Supplier<Function<ServerRequest, Optional<Locale>>> localeResolver() {
|
||||
return () -> DefaultHandlerStrategiesBuilder.DEFAULT_LOCALE_RESOLVER;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,23 +16,9 @@
|
|||
|
||||
package org.springframework.web.reactive.function.server;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
import org.springframework.http.ReactiveHttpOutputMessage;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
|
@ -47,88 +33,16 @@ public class HandlerStrategiesTests {
|
|||
assertEquals(Optional.empty(), strategies.messageReaders().get().findFirst());
|
||||
assertEquals(Optional.empty(), strategies.messageWriters().get().findFirst());
|
||||
assertEquals(Optional.empty(), strategies.viewResolvers().get().findFirst());
|
||||
assertNull(strategies.localeResolver().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ofSuppliers() {
|
||||
HttpMessageReader<?> messageReader = new DummyMessageReader();
|
||||
HttpMessageWriter<?> messageWriter = new DummyMessageWriter();
|
||||
|
||||
HandlerStrategies strategies = HandlerStrategies.empty()
|
||||
.messageReader(messageReader)
|
||||
.messageWriter(messageWriter)
|
||||
.build();
|
||||
|
||||
assertEquals(1L, ((Long) strategies.messageReaders().get().count()).longValue());
|
||||
assertEquals(Optional.of(messageReader), strategies.messageReaders().get().findFirst());
|
||||
|
||||
assertEquals(1L, ((Long) strategies.messageWriters().get().count()).longValue());
|
||||
assertEquals(Optional.of(messageWriter), strategies.messageWriters().get().findFirst());
|
||||
|
||||
public void withDefaults() {
|
||||
HandlerStrategies strategies = HandlerStrategies.withDefaults();
|
||||
assertNotEquals(Optional.empty(), strategies.messageReaders().get().findFirst());
|
||||
assertNotEquals(Optional.empty(), strategies.messageWriters().get().findFirst());
|
||||
assertEquals(Optional.empty(), strategies.viewResolvers().get().findFirst());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toConfiguration() throws Exception {
|
||||
StaticApplicationContext applicationContext = new StaticApplicationContext();
|
||||
applicationContext.registerSingleton("messageWriter", DummyMessageWriter.class);
|
||||
applicationContext.registerSingleton("messageReader", DummyMessageReader.class);
|
||||
applicationContext.refresh();
|
||||
|
||||
HandlerStrategies strategies = HandlerStrategies.of(applicationContext);
|
||||
assertTrue(strategies.messageReaders().get()
|
||||
.allMatch(r -> r instanceof DummyMessageReader));
|
||||
assertTrue(strategies.messageWriters().get()
|
||||
.allMatch(r -> r instanceof DummyMessageWriter));
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class DummyMessageWriter implements HttpMessageWriter<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();
|
||||
}
|
||||
assertNotNull(strategies.localeResolver().get());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue