架构设置不断变化的文本,属性文件?在Java EE环境中

时间:2011-04-25 14:50:27

标签: java internationalization java-ee resourcebundle

在Java EE环境中,我们通常用于在属性/资源文件中存储文本。并且该属性文件与某些视图HTML标记文件相关联。例如。如果您的标签“名字”在HTML页面上更改为“全名”,则可以使用该属性进行更新。

firstName=First Name
someOtherData=This is the data to display on screen, from property file

如果您所处的环境很难定期更新这些属性文件,那么开发人员使用哪种架构来更改通常位于属性文件中的文本/标签内容?或者假设您需要在重新部署属性文件更改之前更改该内容。一个糟糕的解决方案是将其存储在数据库中?开发人员使用memcache吗?这通常用于缓存解决方案吗?

Edit-1 数据库实际上并不是针对此类任务设计的(拉动文本显示在屏幕上),但数据库有用例。我可以添加区域设置列或状态字段,也可以按组添加列过滤器。如果我不使用数据库或属性文件,那么分布式键/值解决方案将允许我添加自定义过滤器?

Edit-2 您是否会使用java框架之外的解决方案?像键/值数据存储一样? memcachedb?

5 个答案:

答案 0 :(得分:11)

我想向您保证,如果您需要对本地化文本进行不断更改,例如,从部署到部署,它们往往会有所不同,那么数据库就是您的选择。好吧,不仅仅是数据库,你需要以某种方式缓存你的字符串。当然,我想你不想完全重建你的资源访问层。

为此,我建议扩展ResourceBundle类以自动从数据库加载字符串并将其存储在WeakHashMap中。我选择WeakHashMap因为它的功能 - 它不再需要时从地图中删除一个键减少内存占用。无论如何,您需要创建一个访问者类。既然你提到了J2EE,这是非常古老的技术,我会给你Java SE 1.4兼容的例子(可以很容易地为新的Java重新编写,只需在需要时添加@Override并在枚举中添加一些字符串泛化):

public class WeakResourceBundle extends ResourceBundle {
    private Map cache = new WeakHashMap();
    protected Locale locale = Locale.US; // default fall-back locale

    // required - Base is abstract
    // @Override
    protected Object handleGetObject(String key) {
        if (cache.containsKey(key))
            return cache.get(key);

        String value = loadFromDatabase(key, locale);
        cache.put(key, value);

        return value;
    }

    // required - Base is abstract
    // @Override
    public Enumeration getKeys() {
        return loadKeysFromDatabase();
    }

    // optional but I believe needed
    // @Override
    public Locale getLocale() {
        return locale;
    }

    // dummy testing method, you need to provide your own
    // should throw MissingResourceException if key does not exist
    private String loadFromDatabase(String key, Locale aLocale) {
        System.out.println("Loading key: " + key
                + " from database for locale:"
                + aLocale );

        return "dummy_" + aLocale.getDisplayLanguage(aLocale);
    }

    // dummy testing method, you need to provide your own
    private Enumeration loadKeysFromDatabase() {
        return Collections.enumeration(new ArrayList());
    }
}

由于一些奇怪的ResourceBundle加载规则,你实际上需要扩展WeakResourceBundle类来为受支持的语言创建一个类:

// Empty Base class for Invariant Language (usually English-US) resources
// Do not need to modify anything here since I already set fall-back language
package com.example.i18n;

public class MyBundle extends WeakResourceBundle {

}

每种语言都支持一种语言(我知道它很糟糕):

// Example class for Polish ResourceBundles
package com.example.i18n;

import java.util.Locale;

public class MyBundle_pl extends WeakResourceBundle {

    public MyBundle_pl() {
        super();
        locale = new Locale("pl");
    }
}

现在,如果您需要实例化ResourceBundle,则只能致电:

// You probably need to get Locale from web browser
Locale polishLocale = new Locale("pl", "PL");
ResourceBundle myBundle = ResourceBundle.getBundle(
                "com.example.i18n.MyBundle", polishLocale);

要访问密钥:

String someValue = myBundle.getString("some.key");

可能的陷阱:

  1. ResourceBundle需要完全合格的类名(因此包名称)。
  2. 如果省略Locale参数,将使用默认值(即服务器)Locale。确保在实例化Locale时始终通过ResourceBundle
  3. 如果您按照我的建议,
  4. myBundle.getString()可以抛出MissingResourceException。您需要使用try-catch块来避免问题。相反,如果缺少密钥(例如return "!" + key + "!"),您可能决定从数据库访问层返回一些虚拟字符串,但无论哪种方式,它都应该记录为错误。
  5. 您应该始终尝试创建传递语言和国家/地区代码的Locale对象。这只是因为,例如简体中文(zh_CN)和繁体中文(zh_TW)等语言是完全不同的语言(至少在写作方面),你需要支持它们的两种风格。对于其他国家/地区,ResourceBundle实际上会自动加载正确的语言资源(请注意,我创建了MyBundle_pl.java,而不是MyBundle_pl_PL.java,它仍然有效。此外,ResourceBundle会自动回退到Enlish-US({{ 1}})如果给定语言没有资源类(这就是我使用这种奇怪的类层次结构的原因)。

  6. 修改

    关于如何使其更加 awsome 的一些随机想法。

    静态工厂(避免直接使用ResourceBundle)

    您可以添加静态工厂方法,而不是直接使用ResourceBundle实例化包:

    MyBundle.java

    如果您决定将public static ResourceBundle getInstance(Locale aLocale) { return ResourceBundle.getBundle("com.example.i18n.MyBundle", aLocale); } 类的名称更改为更合适的名称(我决定使用WeakResourceBundle),您现在可以通过使用代码轻松实例化您的包:

    LocalizationProvider

    自动生成的资源类

    可以通过构建脚本轻松生成本地化ResourceBundle myBundle = LocalizationProvider.getInstance(polishLocale); 类。该脚本可以是配置文件或数据库驱动的 - 它需要知道系统中正在使用哪个Locale。无论哪种方式,这些类共享非常相似的代码,因此自动生成它们确实很有意义。

    自动检测区域设置

    由于您是实现该类的人,因此您可以完全控制其行为。因此(了解您的应用程序架构)您可以在此处包含区域设置检测,并修改MyBundle以自动加载适当的语言资源。

    实施其他与本地化相关的方法

    需要在本地化应用程序中完成常见任务 - 格式化和解析日期,数字,货币等是常见示例。拥有最终用户的Locale,您只需将这些方法包装在LocalizationProvider中。

    哎呀,我真的 我的工作:)

答案 1 :(得分:4)

您谈论属性文件,但在执行时,您可能有资源包或需要键/值对列表的东西(甚至可能取决于区域设置)

您可以以任何格式存储数据,然后使用它来构建正确的资源包。即使它来自记忆。因此,数据库可以完美地做到这一点,因为,属性将在启动时加载,缓存在JVM内存中,这就是全部。 (SELECT * FROM LOCALIZATION_DATA)

不要使用分布式缓存,无论如何,你拥有的数据集可能很小......什么?也许最坏的几MB。并且一旦加载就必须立即访问该数据,因为所有视图都会触发对它的访问,甚至每页都有数百个时间。

如果要在不重新启动应用程序的情况下更新数据,只需在某处添加“重新加载本地化数据”的管理屏幕,或者甚至是允许更新此类数据的屏幕(但保存到文件/数据库/等等) )

从工作流程的角度来看,它取决于您要实现的目标。

经典属性文件是这样做的首选方式。您将其与源代码一起放入版本控制中,以便始终使用代码更新转换。你想调试V1.3.0.1吗?只需获取此版本,您将使用此时使用的属性文件。您添加了需要新密钥的新代码?或者只是因为某种原因改变了关键名称?您知道代码和您的位置信息被链接到一个相关的状态。这是自动的。

如果您的数据不受版本控制,则会丢失自动版本控制和数据历史记录。当您部署/重新部署到新计算机时,可能会出现差异甚至阻止应用程序运行(如果需要新密钥但未添加。这不是很好,容易出错和更多手动干预。

如果你真的需要实时更新,并且真的无法发布新版本,那么我要做的就是为你的数据提供两个来源。标准数据,在版本控制下,因此您确定所有这些都适用于从头开始的新安装。以及可以覆盖标准值的服务器中的“自定义数据”。从版本更新到版本时,自定义值不会丢失,因为这只是更新的标准值。

如果服务器中的更改纯粹是单拍定制,那么您只需转到正确的管理网页,使用自定义本地化数据屏幕即可。

如果更改是您要为任何新安装保留的内容,则将其添加2次。一次在服务器中,一次在版本控制中。

答案 2 :(得分:1)

您可以随时使用JNDI,甚至可以考虑像JCR这样的文档存储库。

答案 3 :(得分:1)

不太确定数据库无法处理这个问题,我认为你真正想要的是一个缓存,当这些属性发生变化时可以失效。您是否考虑过使用JBoss Infinispan(http://www.jboss.org/infinispan)之类的东西?它使用起来非常简单,并且可以分布在多个应用程序服务器上。

Infinispan一旦配置,就可以像地图一样使用;请记住,您可以将其配置为分布在群集中!

如果您不介意使用NoSQL解决方案,我会推荐像Redis或Memcache这样的东西。当然,我会主张你保留一个本地缓存(为什么会产生网络呼叫的成本,特别是如果这些属性不太可能经常改变?)。

答案 4 :(得分:1)

根据柏林布朗的要求,我添加了另一个答案,更多关注其具体需求:

根据您需要的数据量(如数千个条目),您只需在启动时通过指定远程URL加载属性文件。

数据缓存在JVM内存中以获得最佳性能。

根据您的工作流程,您可以使用后台流程以标准方式检查更新(假设每分钟,小时,对您来说足够了),或者您可以在管理中使用“按钮”“刷新本地化数据”在需要更新时使用开发者。

不需要数据库。不需要memcached,不需要NoSQL。可从生产服务器访问的简单URL。在安全性和开发方面,它更容易,更快速,更灵活。

实施细节:如果您使用标准格式,您将拥有每种语言/文件的文件。不要忘记更新所有语言或将它们捆绑在一起(例如使用zip)。