如何从Spring MessageSource解析带命名参数的消息
解决MessageSource.getMessage解析命名参数报错的问题
问题背景
资源包中的消息使用了命名参数格式:
my.key=A message specifying min: {min} and max: {max} parameters
该消息配合@Length注解、Thymeleaf及校验器可正常解析显示,但直接调用MessageSource.getMessage()时抛出IllegalArgumentException(根源为NumberFormatException: can't parse argument number: min),尝试传入Map或错误格式的参数均无效。
原因分析
- Spring默认的
MessageSource实现(如ResourceBundleMessageSource)依赖JDK的MessageFormat工具,仅支持索引占位符({0}、{1}),无法识别命名占位符({min}、{max})。 - Spring Validation校验框架(配合@Length等注解)内部使用了独立的消息解析逻辑,天然支持命名参数,因此能正常解析。直接调用
MessageSource.getMessage()时,MessageFormat会将{min}尝试解析为数字索引,导致格式解析错误。
解决方案
方案1:修改资源文件为索引占位符(最简单)
将资源文件中的命名参数改为索引格式:
my.key=A message specifying min: {0} and max: {1} parameters
调用时按顺序传入参数数组:
messageSource.getMessage("my.key", new Object[]{4, 16}, validLocale);
方案2:自定义MessageSource支持命名参数
若需保留命名参数格式,可自定义MessageSource子类实现命名参数替换逻辑:
方式2.1:轻量字符串替换(适用于简单场景)
import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.util.StringUtils; import java.util.Map; import java.util.Locale; public class NamedParamMessageSource extends ResourceBundleMessageSource { public String getMessage(String code, Map<String, Object> params, Locale locale) { String rawMsg = super.getMessage(code, null, locale); if (!StringUtils.hasText(rawMsg) || params == null || params.isEmpty()) { return rawMsg; } // 遍历替换所有命名参数 for (Map.Entry<String, Object> entry : params.entrySet()) { rawMsg = rawMsg.replace("{" + entry.getKey() + "}", entry.getValue().toString()); } return rawMsg; } }
调用示例:
namedParamMessageSource.getMessage("my.key", Map.of("min", 4, "max", 16), validLocale);
方式2.2:使用Apache Commons Text处理复杂场景
若参数包含{、}等特殊字符,建议用StrSubstitutor处理:
import org.apache.commons.text.StrSubstitutor; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.util.StringUtils; import java.util.Map; import java.util.Locale; public class NamedParamMessageSource extends ResourceBundleMessageSource { public String getMessage(String code, Map<String, Object> params, Locale locale) { String rawMsg = super.getMessage(code, null, locale); if (!StringUtils.hasText(rawMsg) || params == null || params.isEmpty()) { return rawMsg; } return StrSubstitutor.replace(rawMsg, params); } }
需引入Apache Commons Text依赖(Maven):
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-text</artifactId> <version>1.10.0</version> </dependency>
方案3:复用Spring Validation的消息解析器
如果是校验相关场景,可直接使用Spring Validation内置的MessageInterpolator,它和@Length注解的解析逻辑完全一致:
import jakarta.validation.MessageInterpolator; import jakarta.validation.Validation; import org.springframework.context.MessageSource; // ... MessageSource messageSource = // 你的MessageSource实例 Locale validLocale = // 目标Locale // 获取Validation的消息解析器 MessageInterpolator interpolator = Validation.buildDefaultValidatorFactory().getMessageInterpolator(); // 解析消息 String rawMsg = messageSource.getMessage("my.key", null, validLocale); String resolvedMsg = interpolator.interpolate(rawMsg, new MessageInterpolator.Context() { @Override public jakarta.validation.metadata.ConstraintDescriptor<?> getConstraintDescriptor() { return null; } @Override public Object getValidatedValue() { return null; } @Override public <T> T unwrap(Class<T> type) { return null; } }, validLocale);
内容的提问来源于stack exchange,提问作者hicnar
相关产品推荐
相关产品推荐

