Introduce LocaleContextResolver in WebFlux
This commit introduces LocaleContextResolver interface, which is used at ServerWebExchange level to resolve Locale, TimeZone and other i18n related informations. It follows Spring MVC locale resolution patterns with a few differences: - Only LocaleContextResolver is supported since LocaleResolver is less flexible - Support is implemented in the org.springframework.web.server.i18n package of spring-web module rather than in spring-webflux in order to be able to leverage it at ServerWebExchange level 2 implementations are provided: - FixedLocaleContextResolver - AcceptHeaderLocaleContextResolver It can be configured with both functional or annotation-based APIs. Issue: SPR-15036
This commit is contained in:
@@ -58,9 +58,11 @@ import org.springframework.web.reactive.result.method.annotation.ResponseBodyRes
|
||||
import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler;
|
||||
import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebExceptionHandler;
|
||||
import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
|
||||
/**
|
||||
* The main class for Spring WebFlux configuration.
|
||||
@@ -266,6 +268,18 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
|
||||
return serverCodecConfigurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to plug a sub-class of {@link LocaleContextResolver}.
|
||||
*/
|
||||
protected LocaleContextResolver createLocaleContextResolver() {
|
||||
return new AcceptHeaderLocaleContextResolver();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocaleContextResolver localeContextResolver() {
|
||||
return createLocaleContextResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to configure the HTTP message readers and writers to use.
|
||||
*/
|
||||
|
||||
@@ -27,9 +27,11 @@ import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||
import org.springframework.web.server.WebExceptionHandler;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link HandlerStrategies.Builder}.
|
||||
@@ -47,6 +49,8 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
||||
|
||||
private final List<WebExceptionHandler> exceptionHandlers = new ArrayList<>();
|
||||
|
||||
private LocaleContextResolver localeContextResolver;
|
||||
|
||||
|
||||
public DefaultHandlerStrategiesBuilder() {
|
||||
this.codecConfigurer.registerDefaults(false);
|
||||
@@ -55,6 +59,7 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
||||
public void defaultConfiguration() {
|
||||
this.codecConfigurer.registerDefaults(true);
|
||||
exceptionHandler(new ResponseStatusExceptionHandler());
|
||||
localeContextResolver(new AcceptHeaderLocaleContextResolver());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,11 +99,18 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerStrategies.Builder localeContextResolver(LocaleContextResolver localeContextResolver) {
|
||||
Assert.notNull(localeContextResolver, "'localeContextResolver' must not be null");
|
||||
this.localeContextResolver = localeContextResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerStrategies build() {
|
||||
return new DefaultHandlerStrategies(this.codecConfigurer.getReaders(),
|
||||
this.codecConfigurer.getWriters(), this.viewResolvers, this.webFilters,
|
||||
this.exceptionHandlers);
|
||||
this.exceptionHandlers, this.localeContextResolver);
|
||||
}
|
||||
|
||||
|
||||
@@ -114,18 +126,23 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
||||
|
||||
private final List<WebExceptionHandler> exceptionHandlers;
|
||||
|
||||
private final LocaleContextResolver localeContextResolver;
|
||||
|
||||
|
||||
public DefaultHandlerStrategies(
|
||||
List<HttpMessageReader<?>> messageReaders,
|
||||
List<HttpMessageWriter<?>> messageWriters,
|
||||
List<ViewResolver> viewResolvers,
|
||||
List<WebFilter> webFilters,
|
||||
List<WebExceptionHandler> exceptionHandlers) {
|
||||
List<WebExceptionHandler> exceptionHandlers,
|
||||
LocaleContextResolver localeContextResolver) {
|
||||
|
||||
this.messageReaders = unmodifiableCopy(messageReaders);
|
||||
this.messageWriters = unmodifiableCopy(messageWriters);
|
||||
this.viewResolvers = unmodifiableCopy(viewResolvers);
|
||||
this.webFilters = unmodifiableCopy(webFilters);
|
||||
this.exceptionHandlers = unmodifiableCopy(exceptionHandlers);
|
||||
this.localeContextResolver = localeContextResolver;
|
||||
}
|
||||
|
||||
private static <T> List<T> unmodifiableCopy(List<? extends T> list) {
|
||||
@@ -156,6 +173,11 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
|
||||
public List<WebExceptionHandler> exceptionHandlers() {
|
||||
return this.exceptionHandlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleContextResolver localeContextResolver() {
|
||||
return this.localeContextResolver;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
@@ -156,7 +155,7 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
writeStatusAndHeaders(response);
|
||||
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
|
||||
Locale locale = resolveLocale(exchange);
|
||||
Locale locale = exchange.getLocaleContext().getLocale();
|
||||
Stream<ViewResolver> viewResolverStream = context.viewResolvers().stream();
|
||||
|
||||
return Flux.fromStream(viewResolverStream)
|
||||
@@ -167,11 +166,6 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder {
|
||||
.flatMap(view -> view.render(model(), contentType, exchange));
|
||||
}
|
||||
|
||||
private Locale resolveLocale(ServerWebExchange exchange) {
|
||||
List<Locale> locales = exchange.getRequest().getHeaders().getAcceptLanguageAsLocales();
|
||||
return locales.isEmpty() ? Locale.getDefault() : locales.get(0);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||
import org.springframework.web.server.WebExceptionHandler;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
|
||||
@@ -35,6 +36,7 @@ import org.springframework.web.server.WebFilter;
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
* @see RouterFunctions#toHttpHandler(RouterFunction, HandlerStrategies)
|
||||
*/
|
||||
@@ -72,6 +74,12 @@ public interface HandlerStrategies {
|
||||
*/
|
||||
List<WebExceptionHandler> exceptionHandlers();
|
||||
|
||||
/**
|
||||
* Return the {@link LocaleContextResolver} to be used for resolving locale context.
|
||||
* @return the locale context resolver
|
||||
*/
|
||||
LocaleContextResolver localeContextResolver();
|
||||
|
||||
|
||||
// Static methods
|
||||
|
||||
@@ -146,6 +154,13 @@ public interface HandlerStrategies {
|
||||
*/
|
||||
Builder exceptionHandler(WebExceptionHandler exceptionHandler);
|
||||
|
||||
/**
|
||||
* Add the given locale context resolver to this builder.
|
||||
* @param localeContextResolver the locale context resolver to add
|
||||
* @return this builder
|
||||
*/
|
||||
Builder localeContextResolver(LocaleContextResolver localeContextResolver);
|
||||
|
||||
/**
|
||||
* Builds the {@link HandlerStrategies}.
|
||||
* @return the built strategies
|
||||
|
||||
@@ -199,6 +199,7 @@ public abstract class RouterFunctions {
|
||||
return WebHttpHandlerBuilder.webHandler(webHandler)
|
||||
.filters(strategies.webFilters())
|
||||
.exceptionHandlers(strategies.exceptionHandlers())
|
||||
.localeContextResolver(strategies.localeContextResolver())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ import java.util.TimeZone;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceResolvable;
|
||||
import org.springframework.context.NoSuchMessageException;
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -84,9 +86,10 @@ public class RequestContext {
|
||||
this.model = model;
|
||||
this.messageSource = messageSource;
|
||||
|
||||
List<Locale> locales = exchange.getRequest().getHeaders().getAcceptLanguageAsLocales();
|
||||
this.locale = locales.isEmpty() ? Locale.getDefault() : locales.get(0);
|
||||
this.timeZone = TimeZone.getDefault(); // TODO
|
||||
LocaleContext localeContext = exchange.getLocaleContext();
|
||||
this.locale = localeContext.getLocale();
|
||||
this.timeZone = (localeContext instanceof TimeZoneAwareLocaleContext ?
|
||||
((TimeZoneAwareLocaleContext)localeContext).getTimeZone() : TimeZone.getDefault());
|
||||
|
||||
this.defaultHtmlEscape = null; // TODO
|
||||
this.dataValueProcessor = dataValueProcessor;
|
||||
|
||||
@@ -201,8 +201,7 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
|
||||
Model model = result.getModel();
|
||||
MethodParameter parameter = result.getReturnTypeSource();
|
||||
|
||||
List<Locale> locales = exchange.getRequest().getHeaders().getAcceptLanguageAsLocales();
|
||||
Locale locale = locales.isEmpty() ? Locale.getDefault() : locales.get(0);
|
||||
Locale locale = exchange.getLocaleContext().getLocale();
|
||||
|
||||
Class<?> clazz = valueType.getRawClass();
|
||||
if (clazz == null) {
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -189,8 +188,7 @@ public class FreeMarkerView extends AbstractUrlBasedView {
|
||||
logger.debug("Rendering FreeMarker template [" + getUrl() + "].");
|
||||
}
|
||||
|
||||
List<Locale> locales = exchange.getRequest().getHeaders().getAcceptLanguageAsLocales();
|
||||
Locale locale = locales.isEmpty() ? Locale.getDefault() : locales.get(0);
|
||||
Locale locale = exchange.getLocaleContext().getLocale();
|
||||
|
||||
DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user