如何重写ClassPathXmlApplicationContext的getMessage方法解决消息键大小写问题
当然可以重写getMessage方法来解决这个大小写不匹配的问题!甚至还有几种更优雅的实现思路,我来给你一步步拆解清楚:
方案1:自定义MessageSource实现(继承现有类)
Spring常用的消息源实现是ReloadableResourceBundleMessageSource,我们可以直接继承它,重写消息解析的核心方法,让它支持忽略大小写匹配键。
比如写一个CaseInsensitiveMessageSource:
import org.springframework.context.support.ReloadableResourceBundleMessageSource; import java.util.Locale; import java.util.Objects; public class CaseInsensitiveMessageSource extends ReloadableResourceBundleMessageSource { @Override protected String resolveCodeWithoutArguments(String code, Locale locale) { // 先尝试原键匹配,保证原有逻辑不受影响 String message = super.resolveCodeWithoutArguments(code, locale); if (message != null) { return message; } // 原键找不到时,遍历当前Locale的所有消息键,忽略大小写匹配 var mergedProps = getMergedProperties(locale).getProperties(); return mergedProps.keySet().stream() .filter(key -> key.equalsIgnoreCase(code)) .findFirst() .map(mergedProps::getProperty) .orElse(null); } // 别忘了重写带参数的重载方法,逻辑保持一致 @Override protected MessageFormat resolveCode(String code, Locale locale) { MessageFormat format = super.resolveCode(code, locale); if (format != null) { return format; } var mergedProps = getMergedProperties(locale).getProperties(); return mergedProps.keySet().stream() .filter(key -> key.equalsIgnoreCase(code)) .findFirst() .map(key -> createMessageFormat(mergedProps.getProperty(key), locale)) .orElse(null); } }
然后在你的Spring XML配置里,把原来的messageSource bean替换成这个自定义类:
<bean id="messageSource" class="com.yourpackage.CaseInsensitiveMessageSource"> <property name="basename" value="classpath:messages"/> <!-- 保留你原来的其他配置,比如缓存时间、文件编码等 --> </bean>
方案2:用装饰器模式包装现有MessageSource
如果不想继承Spring的原生类,也可以用装饰器模式包装原有的消息源,这种方式更灵活,也不会破坏原有类的结构:
import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceResolvable; import org.springframework.context.NoSuchMessageException; import org.springframework.context.support.ReloadableResourceBundleMessageSource; import java.util.Locale; import java.util.Objects; public class CaseInsensitiveMessageSourceWrapper implements MessageSource { private final MessageSource delegate; public CaseInsensitiveMessageSourceWrapper(MessageSource delegate) { this.delegate = Objects.requireNonNull(delegate); } @Override public String getMessage(String code, Object[] args, String defaultMessage, Locale locale) { // 先尝试原键匹配 String message = delegate.getMessage(code, args, null, locale); if (message != null) { return message; } // 原键找不到时,尝试忽略大小写匹配(针对ReloadableResourceBundleMessageSource的实现) if (delegate instanceof ReloadableResourceBundleMessageSource) { var mergedProps = ((ReloadableResourceBundleMessageSource) delegate).getMergedProperties(locale).getProperties(); return mergedProps.keySet().stream() .filter(key -> key.equalsIgnoreCase(code)) .findFirst() .map(key -> delegate.getMessage(key, args, defaultMessage, locale)) .orElse(defaultMessage); } // 如果是其他MessageSource实现,直接返回默认消息 return defaultMessage; } @Override public String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException { try { return delegate.getMessage(code, args, locale); } catch (NoSuchMessageException e) { if (delegate instanceof ReloadableResourceBundleMessageSource) { var mergedProps = ((ReloadableResourceBundleMessageSource) delegate).getMergedProperties(locale).getProperties(); return mergedProps.keySet().stream() .filter(key -> key.equalsIgnoreCase(code)) .findFirst() .map(key -> delegate.getMessage(key, args, locale)) .orElseThrow(() -> new NoSuchMessageException(code, locale)); } throw e; } } @Override public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException { try { return delegate.getMessage(resolvable, locale); } catch (NoSuchMessageException e) { // 遍历Resolvable中的所有code,逐个尝试忽略大小写匹配 for (String code : resolvable.getCodes()) { try { return getMessage(code, resolvable.getArguments(), locale); } catch (NoSuchMessageException ignored) {} } throw e; } } }
对应的XML配置需要先定义原生消息源,再用包装器包裹它:
<bean id="originalMessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:messages"/> </bean> <bean id="messageSource" class="com.yourpackage.CaseInsensitiveMessageSourceWrapper"> <constructor-arg ref="originalMessageSource"/> </bean>
关键注意事项
- 性能优化:如果你的消息键数量很多,遍历所有键的方式会有一定性能开销。建议在自定义消息源初始化时,提前把所有键转成小写(或大写)存入一个Map,比如
Map<String, String> keyMapping,键是原键的小写形式,值是原键。后续查找时,直接把传入的code转小写,从Map中拿到原键再去匹配,这样就能做到O(1)的查找效率。 - 冲突处理:如果消息文件中存在大小写不同但内容相同的键(比如
USER_NAME和user_name),这种情况会匹配到第一个遍历到的键。建议提前清理这类重复键,避免出现不可预期的结果。 - 方法覆盖完整性:确保你覆盖了
getMessage的所有重载方法,包括带参数、带默认消息以及MessageSourceResolvable的版本,避免遗漏某些场景。
内容的提问来源于stack exchange,提问作者yassadi
相关产品推荐
相关产品推荐

