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);

【讨论】:

好吧,您可以通过将自定义消息源设置为项目的默认值来消除一些丑陋。很难将所有内容都放在评论中,但 &lt;bean id="messageSource" class="com.yourproj.support.ExposedResourceBundleMessageSource"&gt; 在您的配置中,然后在您的班级中 @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】:

我的解决方案:

    使用弹簧&lt;util:properties /&gt;

    <util:properties id="message" location="classpath:messages.properties" />
    

    添加一个拦截器,将“消息”设置为请求属性

    request.setAttribute("msgKeys", RequestContextUtils.getWebApplicationContext(request).getBean("message"));
    

    在JSP中,使用msgKeys通过&lt;fmt:message /&gt;标签检索消息

    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&lt;String, Map&lt;String, String&gt;&gt;),杰克逊会将其序列化为 JSON。我认为它比动态 Javascript-file 选项更好。无论如何,这个解决方案最终也会执行您尝试对动态 Javascript 文件执行的操作,即将键与其解析的值相关联。

【讨论】:

以上是关于Spring - 获取所有可解析的消息键的主要内容,如果未能解决你的问题,请参考以下文章

rabbitmq - 不会获取队列中的所有消息

解析 plist 文件并获取键的值

获取Java接口的所有实现类

RabbitMQ 部分API解析

Spring数据Cassandra可以与动态模​​型和键空间一起使用吗

chrome DevTools使用技巧