Spring - 获取所有可解析的消息键
Posted
技术标签:
【中文标题】Spring - 获取所有可解析的消息键【英文标题】:Spring - get all resolvable message keys 【发布时间】:2011-10-18 10:08:19 【问题描述】:我有一个使用 Spring MVC 的 Web 应用程序。我想让我的界面只包含一个页面,该页面通过 AJAX 以 JSON 形式动态检索所有数据。我的问题是国际化。当我在 jsps 中渲染内容时,我可以使用 JSTL 标签来解析我的键(使用 Spring MVC 超级简单):
<fmt:message key="name"/>: $name
<fmt:message key="title"/>: $title
<fmt:message key="group"/>: $group
如果配置正确,它会在芬兰语言环境中呈现为
Nimi: yay
Otsikko: hoopla
Ryhmä: doo
现在,当我使用 json 时,我只有来自服务器的这个:
name: "yay",
title: "hoopla",
group: "doo"
没有键名!但我必须以某种方式提供它们。我考虑将键名更改为其本地化形式或将本地化键名添加到 json 输出(例如 name_localized="Nimi"),但这两个选项都感觉不好。我正在使用 jackson json 自动将我的域对象解析为 json,我喜欢它提供的封装。
我想出的唯一可行的解决方案是:动态创建一个带有本地化键名作为变量的 javascript 文件。
<script type="text/javascript">
var name="Nimi";
var title="Otsikko";
var group="Ryhmä";
</script>
一旦我加载了这个,我现在在我的 javascript 中拥有所有信息来处理 json!但是有一个问题:我的字段名称列表是动态的。所以实际上jsp中的静态渲染应该是这样的:
<c:forEach var="field" values="$fields">
<fmt:message key="$field.key"/>: $field.value
</c:forEach>
我需要找到我的messages.properties 中指定的所有消息。 Spring MessageSource 接口仅支持按键检索消息。如何在我的 JSP 中获取呈现本地化 javascript 变量的键名列表?
【问题讨论】:
【参考方案1】:好吧,我通过扩展 ResourceBundleMessageSource “解决了”我的问题。
package org.springframework.context.support;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;
public class ExposedResourceBundleMessageSource extends
ResourceBundleMessageSource
public Set<String> getKeys(String basename, Locale locale)
ResourceBundle bundle = getResourceBundle(basename, locale);
return bundle.keySet();
现在我可以访问密钥了,但除了必须指定消息源基本名称之外,我还必须在控制器中进行丑陋的转换。呃,这是很多耦合。
Set<String> keys =
((ExposedResourceBundleMessageSource)
messageSource).getKeys("messages", locale);
【讨论】:
好吧,您可以通过将自定义消息源设置为项目的默认值来消除一些丑陋。很难将所有内容都放在评论中,但<bean id="messageSource" class="com.yourproj.support.ExposedResourceBundleMessageSource">
在您的配置中,然后在您的班级中 @Autowired private ExposedResourceBundleMessageSourcemessageSource;
。
对于那些寻找如何获得Locale
的人,您可能想使用LocaleContextHolder.getLocale()
。这将使语言环境绑定到线程,这通常是您在 servlet/spring mvc 环境中想要的。【参考方案2】:
我已经解决了这个问题。
public class ExposedResourceBundleMessageSource extends
ResourceBundleMessageSource
public static final String WHOLE = "whole";
private Set baseNames;
private Map> cachedData = new HashMap>();
public Set getKeys(String baseName, Locale locale)
ResourceBundle bundle = getResourceBundle(baseName, locale);
return bundle.keySet();
public Map getKeyValues(String basename, Locale locale)
String cacheKey = basename + locale.getCountry();
if (cachedData.containsKey(cacheKey))
return cachedData.get(cacheKey);
ResourceBundle bundle = getResourceBundle(basename, locale);
TreeMap treeMap = new TreeMap();
for (String key : bundle.keySet())
treeMap.put(key, getMessage(key, null, locale));
cachedData.put(cacheKey, treeMap);
return treeMap;
public Map getKeyValues(Locale locale)
String cacheKey = WHOLE + locale.getCountry();
if (cachedData.containsKey(cacheKey))
return cachedData.get(cacheKey);
TreeMap treeMap = new TreeMap();
for (String baseName : baseNames)
treeMap.putAll(getKeyValues(baseName, locale));
cachedData.put(cacheKey, treeMap);
return treeMap;
public void setBasenames(String[] basenames)
baseNames = CollectionUtils.arrayAsSet(basenames);
super.setBasenames(basenames);
【讨论】:
这是一个很好的解决方案,因为添加了缓存和自动捕获基本名称。 但是,如果您在同一国家/地区支持多种语言,则使用 locale.getCountry() 可能会出现问题。仅使用语言环境本身可能更安全。【参考方案3】:我的解决方案:
使用弹簧<util:properties />
<util:properties id="message" location="classpath:messages.properties" />
添加一个拦截器,将“消息”设置为请求属性
request.setAttribute("msgKeys", RequestContextUtils.getWebApplicationContext(request).getBean("message"));
在JSP中,使用msgKeys
通过<fmt:message />
标签检索消息
var msg = <c:forEach items="$msgKeys" var="m" varStatus="s">
<c:set var="key" value="$fn:substringBefore(m,'=')"/>
"$fn:substringAfter(key)":"<fmt:message key="$key"/>",
</c:forEach>;
(当然你需要进一步转义输出以匹配js字符串。)
所以,对于属性
app.name=Application Name
app.title=Some title
会输出
var msg = "name":"Application Name", "title":"Some title" ;
这样做的好处是您可以在c:forEach
循环中执行更多逻辑。
【讨论】:
【参考方案4】:如果你不想扩展 ResourceBundleMessageSource
类,你可以这样做:
@Component
class FrontendMessageLoader
import scala.collection.JavaConverters._
@Resource(name = "messageSource")
var properties: ResourceBundleMessageSource = _
@PostConstruct def init(): Unit =
val locale = new Locale("en", "US")
val baseNames: mutable.Set[String] = properties.getBasenameSet.asScala
val messageMap: Map[String, String] =
baseNames.flatMap baseName =>
readMessages(baseName, locale)
.toMap
private def readMessages(baseName: String, locale: Locale) =
val bundle = ResourceBundle.getBundle(baseName, locale)
bundle.getKeys.asScala.map(key => key -> bundle.getString(key))
这是 Scala,但您明白了,如果需要,您可以轻松地将其转换为 Java。关键是您获取基本名称(即属性文件),然后加载这些文件,然后您就可以获得密钥。
我也传递了语言环境,但ResourceBundle.getBundle
有多个重载版本,您可以选择符合您需要的版本。
【讨论】:
【参考方案5】:我不认为返回本地化值是不好的做法。你可以从你的控制器返回这样的东西(所以你只需要在那里解析密钥):
name: label: "Nimi", value: "yay",
title: label: "Ostikko", value: "hoopla",
group: label: "Rhymä", value: "doo",
您可以创建一个包含这些值的中间对象(最简单的是Map<String, Map<String, String>>
),杰克逊会将其序列化为 JSON。我认为它比动态 Javascript-file 选项更好。无论如何,这个解决方案最终也会执行您尝试对动态 Javascript 文件执行的操作,即将键与其解析的值相关联。
【讨论】:
以上是关于Spring - 获取所有可解析的消息键的主要内容,如果未能解决你的问题,请参考以下文章