diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java
index 799479313a5..ffd6a5a672c 100644
--- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java
+++ b/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java
@@ -49,7 +49,7 @@ import java.beans.PropertyDescriptor;
public interface BeanWrapper extends ConfigurablePropertyAccessor {
/**
- * Specify a limit for array and collection auto-growing.
+ * Specify a limit for array and collection/set/list auto-growing.
*
Default is unlimited on a plain BeanWrapper.
* @since 4.1
*/
diff --git a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java
index 8455682b949..db804b4a031 100644
--- a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java
+++ b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java
@@ -295,8 +295,13 @@ class TypeConverterDelegate {
ClassLoader cl = this.targetObject.getClass().getClassLoader();
try {
Class> enumValueType = ClassUtils.forName(enumType, cl);
- Field enumField = enumValueType.getField(fieldName);
- convertedValue = enumField.get(null);
+ if (enumValueType.isEnum()) {
+ Field enumField = enumValueType.getField(fieldName);
+ convertedValue = enumField.get(null);
+ }
+ else if (logger.isTraceEnabled()) {
+ logger.trace("Specified enum class [" + enumType + "] is not a Java enum");
+ }
}
catch (ClassNotFoundException ex) {
if (logger.isTraceEnabled()) {
@@ -313,8 +318,7 @@ class TypeConverterDelegate {
if (convertedValue == currentConvertedValue) {
// Try field lookup as fallback: for Java enum or custom enum
- // with values defined as static fields. Resulting value still needs
- // to be checked, hence we don't return it right away.
+ // with values defined as static fields.
try {
Field enumField = requiredType.getField(trimmedValue);
ReflectionUtils.makeAccessible(enumField);
diff --git a/spring-context/src/main/java/org/springframework/context/support/MessageSourceSupport.java b/spring-context/src/main/java/org/springframework/context/support/MessageSourceSupport.java
index 93ca113e077..64a59bd4780 100644
--- a/spring-context/src/main/java/org/springframework/context/support/MessageSourceSupport.java
+++ b/spring-context/src/main/java/org/springframework/context/support/MessageSourceSupport.java
@@ -17,15 +17,19 @@
package org.springframework.context.support;
import java.text.MessageFormat;
+import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
/**
* Base class for message source implementations, providing support infrastructure
@@ -41,6 +45,9 @@ import org.springframework.util.ObjectUtils;
*/
public abstract class MessageSourceSupport {
+ static final Set JVM_LOCALES = Arrays.stream(Locale.getAvailableLocales()).
+ filter(l -> StringUtils.hasLength(l.getLanguage())).collect(Collectors.toSet());
+
private static final MessageFormat INVALID_MESSAGE_FORMAT = new MessageFormat("");
/** Logger available to subclasses. */
@@ -116,22 +123,17 @@ public abstract class MessageSourceSupport {
if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
return msg;
}
- Map messageFormatsPerLocale = this.messageFormatsPerMessage
- .computeIfAbsent(msg, key -> new ConcurrentHashMap<>());
- MessageFormat messageFormat = messageFormatsPerLocale.computeIfAbsent(locale, key -> {
- try {
- return createMessageFormat(msg, locale);
- }
- catch (IllegalArgumentException ex) {
- // Invalid message format - probably not intended for formatting,
- // rather using a message structure with no arguments involved...
- if (isAlwaysUseMessageFormat()) {
- throw ex;
- }
- // Silently proceed with raw message if format not enforced...
- return INVALID_MESSAGE_FORMAT;
- }
- });
+
+ MessageFormat messageFormat;
+ if (locale != null && JVM_LOCALES.contains(locale)) {
+ Map messageFormatsPerLocale = this.messageFormatsPerMessage
+ .computeIfAbsent(msg, key -> new ConcurrentHashMap<>());
+ messageFormat = messageFormatsPerLocale.computeIfAbsent(locale, key -> resolveMessageFormat(msg, key));
+ }
+ else {
+ messageFormat = resolveMessageFormat(msg, locale);
+ }
+
if (messageFormat == INVALID_MESSAGE_FORMAT) {
return msg;
}
@@ -140,6 +142,29 @@ public abstract class MessageSourceSupport {
}
}
+ /**
+ * Resolve a {@code MessageFormat} for the given message and Locale.
+ * @param msg the message to create a {@code MessageFormat} for
+ * @param locale the Locale to create a {@code MessageFormat} for
+ * @return the {@code MessageFormat} instance, or otherwise
+ * {@link #INVALID_MESSAGE_FORMAT} if not resolvable
+ * @see #createMessageFormat
+ */
+ private MessageFormat resolveMessageFormat(String msg, @Nullable Locale locale) {
+ try {
+ return createMessageFormat(msg, locale);
+ }
+ catch (IllegalArgumentException ex) {
+ // Invalid message format - probably not intended for formatting,
+ // rather using a message structure with no arguments involved...
+ if (isAlwaysUseMessageFormat()) {
+ throw ex;
+ }
+ // Silently proceed with raw message if format not enforced...
+ return INVALID_MESSAGE_FORMAT;
+ }
+ }
+
/**
* Create a {@code MessageFormat} for the given message and Locale.
* @param msg the message to create a {@code MessageFormat} for
diff --git a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
index 305852097cc..3c2f7bf57f6 100644
--- a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
+++ b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java
@@ -40,6 +40,7 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
+import org.springframework.util.ConcurrentLruCache;
import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.PropertiesPersister;
import org.springframework.util.StringUtils;
@@ -114,15 +115,19 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
private ResourceLoader resourceLoader = new DefaultResourceLoader();
- // Cache to hold filename lists per Locale
+ // Cache to hold filename lists per Locale.
private final ConcurrentMap>> cachedFilenames = new ConcurrentHashMap<>();
- // Cache to hold already loaded properties per filename
+ // Cache to hold already loaded properties per filename.
private final ConcurrentMap cachedProperties = new ConcurrentHashMap<>();
- // Cache to hold already loaded properties per filename
+ // Cache to hold already merged properties per Locale.
private final ConcurrentMap cachedMergedProperties = new ConcurrentHashMap<>();
+ // Cache to hold merged properties per non-JVM Locale.
+ private final ConcurrentLruCache customLocaleProperties =
+ new ConcurrentLruCache<>(64, locale -> mergeProperties(collectPropertiesToMerge(locale)));
+
/**
* Set the list of supported file extensions.
@@ -201,20 +206,16 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
protected @Nullable String resolveCodeWithoutArguments(String code, Locale locale) {
if (getCacheMillis() < 0) {
PropertiesHolder propHolder = getMergedProperties(locale);
- String result = propHolder.getProperty(code);
- if (result != null) {
- return result;
- }
+ return propHolder.getProperty(code);
}
- else {
- for (String basename : getBasenameSet()) {
- List filenames = calculateAllFilenames(basename, locale);
- for (String filename : filenames) {
- PropertiesHolder propHolder = getProperties(filename);
- String result = propHolder.getProperty(code);
- if (result != null) {
- return result;
- }
+
+ for (String basename : getBasenameSet()) {
+ List filenames = calculateAllFilenames(basename, locale);
+ for (String filename : filenames) {
+ PropertiesHolder propHolder = getProperties(filename, locale);
+ String result = propHolder.getProperty(code);
+ if (result != null) {
+ return result;
}
}
}
@@ -229,20 +230,16 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
protected @Nullable MessageFormat resolveCode(String code, Locale locale) {
if (getCacheMillis() < 0) {
PropertiesHolder propHolder = getMergedProperties(locale);
- MessageFormat result = propHolder.getMessageFormat(code, locale);
- if (result != null) {
- return result;
- }
+ return propHolder.getMessageFormat(code, locale);
}
- else {
- for (String basename : getBasenameSet()) {
- List filenames = calculateAllFilenames(basename, locale);
- for (String filename : filenames) {
- PropertiesHolder propHolder = getProperties(filename);
- MessageFormat result = propHolder.getMessageFormat(code, locale);
- if (result != null) {
- return result;
- }
+
+ for (String basename : getBasenameSet()) {
+ List filenames = calculateAllFilenames(basename, locale);
+ for (String filename : filenames) {
+ PropertiesHolder propHolder = getProperties(filename, locale);
+ MessageFormat result = propHolder.getMessageFormat(code, locale);
+ if (result != null) {
+ return result;
}
}
}
@@ -265,12 +262,18 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
if (mergedHolder != null) {
return mergedHolder;
}
- mergedHolder = mergeProperties(collectPropertiesToMerge(locale));
- PropertiesHolder existing = this.cachedMergedProperties.putIfAbsent(locale, mergedHolder);
- if (existing != null) {
- mergedHolder = existing;
+
+ if (JVM_LOCALES.contains(locale)) {
+ mergedHolder = mergeProperties(collectPropertiesToMerge(locale));
+ PropertiesHolder existing = this.cachedMergedProperties.putIfAbsent(locale, mergedHolder);
+ if (existing != null) {
+ mergedHolder = existing;
+ }
+ return mergedHolder;
+ }
+ else {
+ return this.customLocaleProperties.get(locale);
}
- return mergedHolder;
}
/**
@@ -289,7 +292,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
List filenames = calculateAllFilenames(basenames[i], locale);
for (int j = filenames.size() - 1; j >= 0; j--) {
String filename = filenames.get(j);
- PropertiesHolder propHolder = getProperties(filename);
+ PropertiesHolder propHolder = getProperties(filename, locale);
if (propHolder.getProperties() != null) {
holders.add(propHolder);
}
@@ -338,11 +341,11 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
}
}
- // Filenames for given Locale
+ // Filenames for given Locale.
List filenames = new ArrayList<>(7);
filenames.addAll(calculateFilenamesForLocale(basename, locale));
- // Filenames for default Locale, if any
+ // Filenames for default Locale, if any.
Locale defaultLocale = getDefaultLocale();
if (defaultLocale != null && !defaultLocale.equals(locale)) {
List fallbackFilenames = calculateFilenamesForLocale(basename, defaultLocale);
@@ -354,24 +357,27 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
}
}
- // Filename for default bundle file
+ // Filename for default bundle file.
filenames.add(basename);
- if (localeMap == null) {
- localeMap = new ConcurrentHashMap<>();
- Map> existing = this.cachedFilenames.putIfAbsent(basename, localeMap);
- if (existing != null) {
- localeMap = existing;
+ if (JVM_LOCALES.contains(locale)) {
+ if (localeMap == null) {
+ localeMap = new ConcurrentHashMap<>();
+ Map> existing = this.cachedFilenames.putIfAbsent(basename, localeMap);
+ if (existing != null) {
+ localeMap = existing;
+ }
}
+ localeMap.put(locale, filenames);
}
- localeMap.put(locale, filenames);
+
return filenames;
}
/**
* Calculate the filenames for the given bundle basename and Locale,
* appending language code, country code, and variant code.
- *
For example, basename "messages", Locale "de_AT_oo" → "messages_de_AT_OO",
+ *
For example, basename "messages", Locale "de_AT_OO" → "messages_de_AT_OO",
* "messages_de_AT", "messages_de".
*
Follows the rules defined by {@link java.util.Locale#toString()}.
* @param basename the basename of the bundle
@@ -406,6 +412,22 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
}
+ /**
+ * Get a PropertiesHolder for the given filename, either from the
+ * cache or freshly loaded.
+ * @param filename the bundle filename (basename + Locale)
+ * @param locale the requested locale (for cache filtering)
+ * @return the current PropertiesHolder for the bundle
+ * @see #getProperties(String)
+ */
+ private PropertiesHolder getProperties(String filename, Locale locale) {
+ PropertiesHolder propHolder = getProperties(filename);
+ if (propHolder.getProperties() == null && !JVM_LOCALES.contains(locale)) {
+ this.cachedProperties.remove(filename);
+ }
+ return propHolder;
+ }
+
/**
* Get a PropertiesHolder for the given filename, either from the
* cache or freshly loaded.
diff --git a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
index 954d6edb780..2b02ec479e9 100644
--- a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
+++ b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java
@@ -188,36 +188,35 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
* found for the given basename and Locale
*/
protected @Nullable ResourceBundle getResourceBundle(String basename, Locale locale) {
- if (getCacheMillis() >= 0) {
+ if (getCacheMillis() >= 0 || !JVM_LOCALES.contains(locale)) {
// Fresh ResourceBundle.getBundle call in order to let ResourceBundle
// do its native caching, at the expense of more extensive lookup steps.
return doGetBundle(basename, locale);
}
- else {
- // Cache forever: prefer locale cache over repeated getBundle calls.
- Map localeMap = this.cachedResourceBundles.get(basename);
- if (localeMap != null) {
- ResourceBundle bundle = localeMap.get(locale);
- if (bundle != null) {
- return bundle;
- }
- }
- try {
- ResourceBundle bundle = doGetBundle(basename, locale);
- if (localeMap == null) {
- localeMap = this.cachedResourceBundles.computeIfAbsent(basename, bn -> new ConcurrentHashMap<>());
- }
- localeMap.put(locale, bundle);
+
+ // Cache forever: prefer local cache over repeated getBundle calls.
+ Map localeMap = this.cachedResourceBundles.get(basename);
+ if (localeMap != null) {
+ ResourceBundle bundle = localeMap.get(locale);
+ if (bundle != null) {
return bundle;
}
- catch (MissingResourceException ex) {
- if (logger.isWarnEnabled()) {
- logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
- }
- // Assume bundle not found
- // -> do NOT throw the exception to allow for checking parent message source.
- return null;
+ }
+ try {
+ ResourceBundle bundle = doGetBundle(basename, locale);
+ if (localeMap == null) {
+ localeMap = this.cachedResourceBundles.computeIfAbsent(basename, bn -> new ConcurrentHashMap<>());
}
+ localeMap.put(locale, bundle);
+ return bundle;
+ }
+ catch (MissingResourceException ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
+ }
+ // Assume bundle not found
+ // -> do NOT throw the exception to allow for checking parent message source.
+ return null;
}
}
@@ -311,6 +310,11 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
protected @Nullable MessageFormat getMessageFormat(ResourceBundle bundle, String code, Locale locale)
throws MissingResourceException {
+ if (!JVM_LOCALES.contains(locale)) {
+ String msg = getStringOrNull(bundle, code);
+ return (msg != null ? createMessageFormat(msg, locale) : null);
+ }
+
Map> codeMap = this.cachedBundleMessageFormats.get(bundle);
Map localeMap = null;
if (codeMap != null) {
diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java
index 2174afd37e9..61321a0fe53 100644
--- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java
+++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java
@@ -270,12 +270,12 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
/**
- * Specify the limit for array and collection auto-growing.
+ * Specify the limit for array and collection/set/list auto-growing.
*
Default is 256, preventing OutOfMemoryErrors in case of large indexes.
* Raise this limit if your auto-growing needs are unusually high.
*
Used for setter injection - and as of 7.1 also for field injection -
- * via {@link #bind(PropertyValues)}; not applicable to constructor binding
- * via {@link #construct}.
+ * via {@link #bind(PropertyValues)}; not applicable to map properties and
+ * not to constructor binding via {@link #construct} either.
* @see #initBeanPropertyAccess()
* @see org.springframework.beans.BeanWrapper#setAutoGrowCollectionLimit
*/
@@ -326,6 +326,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
/**
* Initialize direct field access for this DataBinder,
* as alternative to the default bean property access.
+ *
NOTE: This is an advanced option for trusted scenarios.
+ * Do not use direct field access for data binding from untrusted sources.
* @see #initBeanPropertyAccess()
* @see #createDirectFieldBindingResult()
*/
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java b/spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java
index f2ee2398c1c..fe343e51474 100644
--- a/spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java
+++ b/spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java
@@ -100,20 +100,20 @@ public class LocalizedResourceHelper {
String variant = locale.getVariant();
// Check for file with language, country and variant localization.
- if (variant.length() > 0) {
+ if (!variant.isEmpty()) {
String location =
name + this.separator + lang + this.separator + country + this.separator + variant + extension;
resource = this.resourceLoader.getResource(location);
}
// Check for file with language and country localization.
- if ((resource == null || !resource.exists()) && country.length() > 0) {
+ if ((resource == null || !resource.exists()) && !country.isEmpty()) {
String location = name + this.separator + lang + this.separator + country + extension;
resource = this.resourceLoader.getResource(location);
}
// Check for document with language localization.
- if ((resource == null || !resource.exists()) && lang.length() > 0) {
+ if ((resource == null || !resource.exists()) && !lang.isEmpty()) {
String location = name + this.separator + lang + extension;
resource = this.resourceLoader.getResource(location);
}
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java b/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java
index 5a3d760edd1..9a66902ce4c 100644
--- a/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java
+++ b/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java
@@ -18,6 +18,7 @@ package org.springframework.core.serializer;
import java.io.IOException;
import java.io.InputStream;
+import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;
import org.jspecify.annotations.Nullable;
@@ -38,6 +39,8 @@ public class DefaultDeserializer implements Deserializer