mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Merge pull request #51484 from henriquejsza
Closes gh-51484 * gh-51484: Add ResourceBasedMessageSourceConfigurer
This commit is contained in:
+12
-43
@@ -16,12 +16,6 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.context;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
@@ -35,7 +29,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.MessageSourceRuntimeHints;
|
||||
import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -44,13 +37,10 @@ import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.PropertiesLoaderUtils;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -62,12 +52,11 @@ import org.springframework.util.StringUtils;
|
||||
* @author Eddú Meléndez
|
||||
* @author Marc Becker
|
||||
* @author Misagh Moayyed
|
||||
* @author Henrique (henriquejsza)
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
|
||||
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
|
||||
@Conditional(ResourceBundleCondition.class)
|
||||
@EnableConfigurationProperties(MessageSourceProperties.class)
|
||||
@ImportRuntimeHints(MessageSourceRuntimeHints.class)
|
||||
public final class MessageSourceAutoConfiguration {
|
||||
@@ -75,39 +64,19 @@ public final class MessageSourceAutoConfiguration {
|
||||
private static final Resource[] NO_RESOURCES = {};
|
||||
|
||||
@Bean
|
||||
MessageSource messageSource(MessageSourceProperties properties) {
|
||||
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
|
||||
if (!CollectionUtils.isEmpty(properties.getBasename())) {
|
||||
messageSource.setBasenames(properties.getBasename().toArray(new String[0]));
|
||||
}
|
||||
if (properties.getEncoding() != null) {
|
||||
messageSource.setDefaultEncoding(properties.getEncoding().name());
|
||||
}
|
||||
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
|
||||
Duration cacheDuration = properties.getCacheDuration();
|
||||
if (cacheDuration != null) {
|
||||
messageSource.setCacheMillis(cacheDuration.toMillis());
|
||||
}
|
||||
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
|
||||
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
|
||||
messageSource.setCommonMessages(loadCommonMessages(properties.getCommonMessages()));
|
||||
return messageSource;
|
||||
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
|
||||
ResourceBasedMessageSourceConfigurer resourceBasedMessageSourceConfigurer(MessageSourceProperties properties) {
|
||||
return new ResourceBasedMessageSourceConfigurer(properties);
|
||||
}
|
||||
|
||||
private @Nullable Properties loadCommonMessages(@Nullable List<Resource> resources) {
|
||||
if (CollectionUtils.isEmpty(resources)) {
|
||||
return null;
|
||||
}
|
||||
Properties properties = CollectionFactory.createSortedProperties(false);
|
||||
for (Resource resource : resources) {
|
||||
try {
|
||||
PropertiesLoaderUtils.fillProperties(properties, resource);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException("Failed to load common messages from '%s'".formatted(resource), ex);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME,
|
||||
search = SearchStrategy.CURRENT)
|
||||
@Conditional(ResourceBundleCondition.class)
|
||||
ResourceBundleMessageSource messageSource(ResourceBasedMessageSourceConfigurer configurer) {
|
||||
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
|
||||
configurer.configure(messageSource);
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
protected static class ResourceBundleCondition extends SpringBootCondition {
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.context;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.context.support.AbstractResourceBasedMessageSource;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PropertiesLoaderUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Configure an {@link AbstractResourceBasedMessageSource} with sensible defaults tuned
|
||||
* using configuration properties.
|
||||
* <p>
|
||||
* Can be injected into application code and used to define a custom message source whose
|
||||
* configuration is based upon that produced by auto-configuration.
|
||||
*
|
||||
* @author Henrique (henriquejsza)
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public class ResourceBasedMessageSourceConfigurer {
|
||||
|
||||
private final MessageSourceProperties properties;
|
||||
|
||||
/**
|
||||
* Creates a new configurer that will use the given {@code properties}.
|
||||
* @param properties properties to use
|
||||
*/
|
||||
public ResourceBasedMessageSourceConfigurer(MessageSourceProperties properties) {
|
||||
Assert.notNull(properties, "'properties' must not be null");
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the specified message source. The message source can be further tuned and
|
||||
* default settings can be overridden.
|
||||
* @param messageSource the {@link AbstractResourceBasedMessageSource} instance to
|
||||
* configure
|
||||
*/
|
||||
public void configure(AbstractResourceBasedMessageSource messageSource) {
|
||||
Assert.notNull(messageSource, "'messageSource' must not be null");
|
||||
if (!CollectionUtils.isEmpty(this.properties.getBasename())) {
|
||||
messageSource.setBasenames(this.properties.getBasename().toArray(new String[0]));
|
||||
}
|
||||
if (this.properties.getEncoding() != null) {
|
||||
messageSource.setDefaultEncoding(this.properties.getEncoding().name());
|
||||
}
|
||||
messageSource.setFallbackToSystemLocale(this.properties.isFallbackToSystemLocale());
|
||||
Duration cacheDuration = this.properties.getCacheDuration();
|
||||
if (cacheDuration != null) {
|
||||
messageSource.setCacheMillis(cacheDuration.toMillis());
|
||||
}
|
||||
messageSource.setAlwaysUseMessageFormat(this.properties.isAlwaysUseMessageFormat());
|
||||
messageSource.setUseCodeAsDefaultMessage(this.properties.isUseCodeAsDefaultMessage());
|
||||
messageSource.setCommonMessages(loadCommonMessages(this.properties.getCommonMessages()));
|
||||
}
|
||||
|
||||
private @Nullable Properties loadCommonMessages(@Nullable List<Resource> resources) {
|
||||
if (CollectionUtils.isEmpty(resources)) {
|
||||
return null;
|
||||
}
|
||||
Properties properties = CollectionFactory.createSortedProperties(false);
|
||||
for (Resource resource : resources) {
|
||||
try {
|
||||
PropertiesLoaderUtils.fillProperties(properties, resource);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException("Failed to load common messages from '%s'".formatted(resource), ex);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -40,8 +40,12 @@ import org.springframework.context.NoSuchMessageException;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link MessageSourceAutoConfiguration}.
|
||||
@@ -53,6 +57,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Marc Becker
|
||||
* @author Misagh Moayyed
|
||||
* @author Phillip Webb
|
||||
* @author Henrique (henriquejsza)
|
||||
*/
|
||||
class MessageSourceAutoConfigurationTests {
|
||||
|
||||
@@ -65,6 +70,44 @@ class MessageSourceAutoConfigurationTests {
|
||||
.isEqualTo("Foo message"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resourceBasedMessageSourceConfigurerIsAvailableWithoutResourceBundle() {
|
||||
this.contextRunner
|
||||
.run((context) -> assertThat(context).hasSingleBean(ResourceBasedMessageSourceConfigurer.class)
|
||||
.doesNotHaveBean(ResourceBundleMessageSource.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resourceBasedMessageSourceConfigurerCanConfigureUserDefinedMessageSource() {
|
||||
this.contextRunner.withUserConfiguration(CustomResourceBasedMessageSourceConfiguration.class)
|
||||
.withPropertyValues("spring.messages.cache-duration=10s", "spring.messages.fallback-to-system-locale=false",
|
||||
"spring.messages.always-use-message-format=true",
|
||||
"spring.messages.use-code-as-default-message=true")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ResourceBasedMessageSourceConfigurer.class)
|
||||
.hasSingleBean(ReloadableResourceBundleMessageSource.class);
|
||||
assertThat(context.getBean(ReloadableResourceBundleMessageSource.class))
|
||||
.hasFieldOrPropertyWithValue("cacheMillis", 10_000L)
|
||||
.hasFieldOrPropertyWithValue("fallbackToSystemLocale", false)
|
||||
.hasFieldOrPropertyWithValue("alwaysUseMessageFormat", true)
|
||||
.hasFieldOrPropertyWithValue("useCodeAsDefaultMessage", true);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithTestMessagesPropertiesResource
|
||||
void autoConfiguredMessageSourceUsesUserDefinedConfigurer() {
|
||||
ResourceBasedMessageSourceConfigurer configurer = mock(ResourceBasedMessageSourceConfigurer.class);
|
||||
this.contextRunner.withBean(ResourceBasedMessageSourceConfigurer.class, () -> configurer)
|
||||
.withPropertyValues("spring.messages.basename=test/messages")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ResourceBasedMessageSourceConfigurer.class)
|
||||
.hasSingleBean(ResourceBundleMessageSource.class);
|
||||
assertThat(context.getBean(ResourceBasedMessageSourceConfigurer.class)).isSameAs(configurer);
|
||||
then(configurer).should().configure(context.getBean(ResourceBundleMessageSource.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithTestMessagesPropertiesResource
|
||||
void propertiesBundleWithSlashIsDetected() {
|
||||
@@ -256,6 +299,18 @@ class MessageSourceAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomResourceBasedMessageSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
ReloadableResourceBundleMessageSource messageSource(ResourceBasedMessageSourceConfigurer configurer) {
|
||||
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||
configurer.configure(messageSource);
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomBeanNameMessageSourceConfiguration {
|
||||
|
||||
|
||||
+2
@@ -23,3 +23,5 @@ TIP: The configprop:spring.messages.basename[] property supports a list of locat
|
||||
The configprop:spring.messages.common-messages[] property supports a list of property file resources.
|
||||
|
||||
See javadoc:org.springframework.boot.autoconfigure.context.MessageSourceProperties[] for more supported options.
|
||||
|
||||
If you need to use a different implementation of javadoc:org.springframework.context.support.AbstractResourceBasedMessageSource[], Spring Boot provides a javadoc:org.springframework.boot.autoconfigure.context.ResourceBasedMessageSourceConfigurer[] bean that you can use to apply the same settings as the auto-configured message source.
|
||||
|
||||
Reference in New Issue
Block a user