Provide config properties for configuring WebFlux's locale resolution
Previously, the locale context resolver used with WebFlux could only be configured by provided a custom LocaleContextResolver bean. By constrast, when using Spring MVC, the spring.mvc.locale and spring.mvc.locale-resolver properties could be used to configure the locale and the resolver (fixed or Accept header) respectively. This commit introduces spring.web.locale and spring.web.locale-resolver properties and deprecates their spring.mvc equivalents. The new properties can be used to configure locale resolution with either Spring MVC or WebFlux. Closes gh-23449
This commit is contained in:
parent
ef89eb6dfb
commit
1c4b4cb0cd
|
|
@ -72,7 +72,7 @@ public class DocumentConfigurationProperties extends DefaultTask {
|
|||
.addSection("mail").withKeyPrefixes("spring.mail", "spring.sendgrid").addSection("cache")
|
||||
.withKeyPrefixes("spring.cache").addSection("server").withKeyPrefixes("server").addSection("web")
|
||||
.withKeyPrefixes("spring.hateoas", "spring.http", "spring.servlet", "spring.jersey", "spring.mvc",
|
||||
"spring.resources", "spring.webflux")
|
||||
"spring.resources", "spring.web", "spring.webflux")
|
||||
.addSection("json").withKeyPrefixes("spring.jackson", "spring.gson").addSection("rsocket")
|
||||
.withKeyPrefixes("spring.rsocket").addSection("templating")
|
||||
.withKeyPrefixes("spring.freemarker", "spring.groovy", "spring.mustache", "spring.thymeleaf")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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
|
||||
*
|
||||
* https://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.boot.autoconfigure.web;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* {@link ConfigurationProperties Configuration properties} for general web concerns.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.web")
|
||||
public class WebProperties {
|
||||
|
||||
/**
|
||||
* Locale to use. By default, this locale is overridden by the "Accept-Language"
|
||||
* header.
|
||||
*/
|
||||
private Locale locale;
|
||||
|
||||
/**
|
||||
* Define how the locale should be resolved.
|
||||
*/
|
||||
private LocaleResolver localeResolver = LocaleResolver.ACCEPT_HEADER;
|
||||
|
||||
public Locale getLocale() {
|
||||
return this.locale;
|
||||
}
|
||||
|
||||
public void setLocale(Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public LocaleResolver getLocaleResolver() {
|
||||
return this.localeResolver;
|
||||
}
|
||||
|
||||
public void setLocaleResolver(LocaleResolver localeResolver) {
|
||||
this.localeResolver = localeResolver;
|
||||
}
|
||||
|
||||
public enum LocaleResolver {
|
||||
|
||||
/**
|
||||
* Always use the configured locale.
|
||||
*/
|
||||
FIXED,
|
||||
|
||||
/**
|
||||
* Use the "Accept-Language" header or the configured locale if the header is not
|
||||
* set.
|
||||
*/
|
||||
ACCEPT_HEADER
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ import org.springframework.boot.autoconfigure.validation.ValidationAutoConfigura
|
|||
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
|
||||
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
|
||||
import org.springframework.boot.autoconfigure.web.ResourceProperties;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
|
||||
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties.Format;
|
||||
|
|
@ -69,6 +70,8 @@ import org.springframework.web.reactive.result.method.annotation.ArgumentResolve
|
|||
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
import org.springframework.web.server.i18n.FixedLocaleContextResolver;
|
||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||
|
||||
/**
|
||||
|
|
@ -216,15 +219,19 @@ public class WebFluxAutoConfiguration {
|
|||
* Configuration equivalent to {@code @EnableWebFlux}.
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(WebProperties.class)
|
||||
public static class EnableWebFluxConfiguration extends DelegatingWebFluxConfiguration {
|
||||
|
||||
private final WebFluxProperties webFluxProperties;
|
||||
|
||||
private final WebProperties webProperties;
|
||||
|
||||
private final WebFluxRegistrations webFluxRegistrations;
|
||||
|
||||
public EnableWebFluxConfiguration(WebFluxProperties webFluxProperties,
|
||||
public EnableWebFluxConfiguration(WebFluxProperties webFluxProperties, WebProperties webProperties,
|
||||
ObjectProvider<WebFluxRegistrations> webFluxRegistrations) {
|
||||
this.webFluxProperties = webFluxProperties;
|
||||
this.webProperties = webProperties;
|
||||
this.webFluxRegistrations = webFluxRegistrations.getIfUnique();
|
||||
}
|
||||
|
||||
|
|
@ -269,7 +276,12 @@ public class WebFluxAutoConfiguration {
|
|||
@Override
|
||||
@ConditionalOnMissingBean
|
||||
public LocaleContextResolver localeContextResolver() {
|
||||
return super.localeContextResolver();
|
||||
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
|
||||
return new FixedLocaleContextResolver(this.webProperties.getLocale());
|
||||
}
|
||||
AcceptHeaderLocaleContextResolver localeContextResolver = new AcceptHeaderLocaleContextResolver();
|
||||
localeContextResolver.setDefaultLocale(this.webProperties.getLocale());
|
||||
return localeContextResolver;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.time.Duration;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
|
|
@ -51,6 +52,7 @@ import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
|
|||
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
|
||||
import org.springframework.boot.autoconfigure.web.ResourceProperties;
|
||||
import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
|
||||
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.Format;
|
||||
|
|
@ -358,23 +360,27 @@ public class WebMvcAutoConfiguration {
|
|||
* Configuration equivalent to {@code @EnableWebMvc}.
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(WebProperties.class)
|
||||
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
|
||||
|
||||
private final ResourceProperties resourceProperties;
|
||||
|
||||
private final WebMvcProperties mvcProperties;
|
||||
|
||||
private final WebProperties webProperties;
|
||||
|
||||
private final ListableBeanFactory beanFactory;
|
||||
|
||||
private final WebMvcRegistrations mvcRegistrations;
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
public EnableWebMvcConfiguration(ResourceProperties resourceProperties,
|
||||
ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
|
||||
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) {
|
||||
public EnableWebMvcConfiguration(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
|
||||
WebProperties webProperties, ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
|
||||
ListableBeanFactory beanFactory) {
|
||||
this.resourceProperties = resourceProperties;
|
||||
this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
|
||||
this.mvcProperties = mvcProperties;
|
||||
this.webProperties = webProperties;
|
||||
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
|
@ -426,13 +432,18 @@ public class WebMvcAutoConfiguration {
|
|||
@Override
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale", matchIfMissing = true)
|
||||
@SuppressWarnings("deprecation")
|
||||
public LocaleResolver localeResolver() {
|
||||
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
|
||||
return new FixedLocaleResolver(this.webProperties.getLocale());
|
||||
}
|
||||
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
|
||||
return new FixedLocaleResolver(this.mvcProperties.getLocale());
|
||||
}
|
||||
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
|
||||
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
|
||||
Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale()
|
||||
: this.mvcProperties.getLocale();
|
||||
localeResolver.setDefaultLocale(locale);
|
||||
return localeResolver;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ public class WebMvcProperties {
|
|||
this.messageCodesResolverFormat = messageCodesResolverFormat;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@DeprecatedConfigurationProperty(replacement = "spring.web.locale")
|
||||
public Locale getLocale() {
|
||||
return this.locale;
|
||||
}
|
||||
|
|
@ -129,6 +131,8 @@ public class WebMvcProperties {
|
|||
this.locale = locale;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@DeprecatedConfigurationProperty(replacement = "spring.web.locale-resolver")
|
||||
public LocaleResolver getLocaleResolver() {
|
||||
return this.localeResolver;
|
||||
}
|
||||
|
|
@ -543,6 +547,12 @@ public class WebMvcProperties {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Locale resolution options.
|
||||
* @deprecated since 2.4.0 in favor of
|
||||
* {@link org.springframework.boot.autoconfigure.web.WebProperties.LocaleResolver}
|
||||
*/
|
||||
@Deprecated
|
||||
public enum LocaleResolver {
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import org.springframework.context.ApplicationContext;
|
|||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
|
|
@ -54,7 +55,10 @@ import org.springframework.format.support.FormattingConversionService;
|
|||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.filter.reactive.HiddenHttpMethodFilter;
|
||||
|
|
@ -74,6 +78,7 @@ import org.springframework.web.reactive.result.method.annotation.RequestMappingH
|
|||
import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
import org.springframework.web.server.i18n.FixedLocaleContextResolver;
|
||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
||||
|
|
@ -454,6 +459,54 @@ class WebFluxAutoConfigurationTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultLocaleContextResolver() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).hasSingleBean(LocaleContextResolver.class);
|
||||
LocaleContextResolver resolver = context.getBean(LocaleContextResolver.class);
|
||||
assertThat(((AcceptHeaderLocaleContextResolver) resolver).getDefaultLocale()).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenFixedLocalContextResolverIsUsedThenAcceptLanguagesHeaderIsIgnored() {
|
||||
this.contextRunner.withPropertyValues("spring.web.locale:en_UK", "spring.web.locale-resolver=fixed")
|
||||
.run((context) -> {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/")
|
||||
.acceptLanguageAsLocales(StringUtils.parseLocaleString("nl_NL")).build();
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
LocaleContextResolver localeContextResolver = context.getBean(LocaleContextResolver.class);
|
||||
assertThat(localeContextResolver).isInstanceOf(FixedLocaleContextResolver.class);
|
||||
LocaleContext localeContext = localeContextResolver.resolveLocaleContext(exchange);
|
||||
assertThat(localeContext.getLocale()).isEqualTo(StringUtils.parseLocaleString("en_UK"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAcceptHeaderLocaleContextResolverIsUsedThenAcceptLanguagesHeaderIsHonoured() {
|
||||
this.contextRunner.withPropertyValues("spring.web.locale:en_UK").run((context) -> {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/")
|
||||
.acceptLanguageAsLocales(StringUtils.parseLocaleString("nl_NL")).build();
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
LocaleContextResolver localeContextResolver = context.getBean(LocaleContextResolver.class);
|
||||
assertThat(localeContextResolver).isInstanceOf(AcceptHeaderLocaleContextResolver.class);
|
||||
LocaleContext localeContext = localeContextResolver.resolveLocaleContext(exchange);
|
||||
assertThat(localeContext.getLocale()).isEqualTo(StringUtils.parseLocaleString("nl_NL"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAcceptHeaderLocaleContextResolverIsUsedAndHeaderIsAbsentThenConfiguredLocaleIsUsed() {
|
||||
this.contextRunner.withPropertyValues("spring.web.locale:en_UK").run((context) -> {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/").build();
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
LocaleContextResolver localeContextResolver = context.getBean(LocaleContextResolver.class);
|
||||
assertThat(localeContextResolver).isInstanceOf(AcceptHeaderLocaleContextResolver.class);
|
||||
LocaleContext localeContext = localeContextResolver.resolveLocaleContext(exchange);
|
||||
assertThat(localeContext.getLocale()).isEqualTo(StringUtils.parseLocaleString("en_UK"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void customLocaleContextResolver() {
|
||||
this.contextRunner.withUserConfiguration(LocaleContextResolverConfiguration.class)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import javax.validation.ValidatorFactory;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
|
|
@ -289,10 +291,11 @@ class WebMvcAutoConfigurationTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideLocale() {
|
||||
this.contextRunner.withPropertyValues("spring.mvc.locale:en_UK", "spring.mvc.locale-resolver=fixed")
|
||||
.run((loader) -> {
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "mvc", "web" })
|
||||
void overrideLocale(String mvcOrWeb) {
|
||||
this.contextRunner.withPropertyValues("spring." + mvcOrWeb + ".locale:en_UK",
|
||||
"spring." + mvcOrWeb + ".locale-resolver=fixed").run((loader) -> {
|
||||
// mock request and set user preferred locale
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.addPreferredLocale(StringUtils.parseLocaleString("nl_NL"));
|
||||
|
|
@ -306,9 +309,10 @@ class WebMvcAutoConfigurationTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void useAcceptHeaderLocale() {
|
||||
this.contextRunner.withPropertyValues("spring.mvc.locale:en_UK").run((loader) -> {
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "mvc", "web" })
|
||||
void useAcceptHeaderLocale(String mvcOrWeb) {
|
||||
this.contextRunner.withPropertyValues("spring." + mvcOrWeb + ".locale:en_UK").run((loader) -> {
|
||||
// mock request and set user preferred locale
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.addPreferredLocale(StringUtils.parseLocaleString("nl_NL"));
|
||||
|
|
@ -321,9 +325,10 @@ class WebMvcAutoConfigurationTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void useDefaultLocaleIfAcceptHeaderNoSet() {
|
||||
this.contextRunner.withPropertyValues("spring.mvc.locale:en_UK").run((context) -> {
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "mvc", "web" })
|
||||
void useDefaultLocaleIfAcceptHeaderNoSet(String mvcOrWeb) {
|
||||
this.contextRunner.withPropertyValues("spring." + mvcOrWeb + ".locale:en_UK").run((context) -> {
|
||||
// mock request and set user preferred locale
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
LocaleResolver localeResolver = context.getBean(LocaleResolver.class);
|
||||
|
|
|
|||
Loading…
Reference in New Issue