3

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

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

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

Edit-1数据库确实不是为这种类型的任务(拉出文本以显示在屏幕上)而设计的,但是有数据库的用例。我可以添加区域设置列或状态字段,还可以按组添加列过滤器。如果我不使用数据库或属性文件,哪些分布式键/值解决方案可以让我添加自定义过滤器?

Edit-2你会使用 java 框架之外的解决方案吗?像键/值数据存储?内存缓存数据库?

4

5 回答 5

11

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

为此,我可以建议扩展ResourceBundle类以自动从数据库加载字符串并将其存储在WeakHashMap. 我之所以选择WeakHashMap它是因为它的特性——当不再需要它时,它会从映射中删除一个键,从而减少内存占用。无论如何,您需要创建一个访问器类。既然您提到了 J2EE,这是一种非常古老的技术,我将给您提供 Java SE 1.4 兼容示例(它可以很容易地为较新的 Java 重新设计,只需在需要时添加 @Override 并在 Enumeration 中添加一些字符串泛化):

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. myBundle.getString()MissingResourceException如果您遵循我的建议,可以抛出。您需要使用 try-catch 块来避免问题。相反,您可以决定在缺少键(如return "!" + key + "!")的情况下从数据库访问层返回一些虚拟字符串,但无论哪种方式,它都应该被记录为错误。
  4. 您应该始终尝试创建同时传递语言和国家代码的 Locale 对象。那是因为,像简体中文 (zh_CN) 和繁体中文 (zh_TW) 这样的语言是完全不同的语言(至少在写作方面),您需要支持它们的两种风格。对于其他国家/地区,ResourceBundle 实际上会自动加载正确的语言资源(请注意,我创建了MyBundle_pl.java,但MyBundle_pl_PL.java它仍然有效。此外,MyBundle.java如果给定语言没有资源类(即这就是为什么我使用如此奇怪的类层次结构)。

编辑

关于如何使它更糟糕的一些随机想法。

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

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

public static ResourceBundle getInstance(Locale aLocale) {
    return ResourceBundle.getBundle("com.example.i18n.MyBundle", aLocale);
}

如果您决定将WeakResourceBundle类的名称更改为更合适的名称(我决定使用LocalizationProvider),您现在可以轻松地从使用代码中实例化您的包:

ResourceBundle myBundle = LocalizationProvider.getInstance(polishLocale);

自动生成的资源类

本地化MyBundle的类可以通过构建脚本轻松生成。该脚本可以是配置文件或数据库驱动的——它需要知道系统中正在使用哪个区域设置。无论哪种方式,这些类共享非常相似的代码,因此自动生成它们真的很有意义。

自动检测语言环境

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

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

在本地化应用程序中需要完成一些常见任务 - 格式化和解析日期、数字、货币等是常见的示例。有了最终用户的区域设置,您可以简单地将此类方法包装在 LocalizationProvider 中。

哎呀,我真的很喜欢我的工作:)

于 2011-05-01T21:22:46.663 回答
4

您谈论属性文件,但在执行时,您可能有一个资源包或需要键/值对列表的东西(甚至可能取决于语言环境)

您可以以任何格式存储数据,然后使用它来构建正确的资源包。即使它来自记忆。所以数据库可以完美地做到这一点,因为属性都将在启动时加载,缓存在 JVM 内存中,仅此而已。(从本地化数据中选择 *)

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

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

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

经典属性文件是执行此操作的首选方式。您将其与源代码一起放入版本控制中,这样您的翻译始终与代码保持同步。你想调试 V1.3.0.1 吗?只需获取此版本,您将使用此时使用的属性文件。您添加了需要新密钥的新代码?或者只是出于某种原因更改了他们的键名?您知道代码和您的定位信息链接到一个连贯的状态。这是自动的。

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

如果您真的需要实时更新,并且真的不能为此发布新版本,我会做的是为您的数据提供两个来源。标准数据,受版本控制,因此您确定一切都适合从头开始新安装。以及可以覆盖标准值的服务器中的“自定义数据”。从版本更新到版本时,自定义值不会丢失,因为这只是更新的标准值。

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

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

于 2011-04-28T08:46:28.653 回答
1

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

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

如果您不介意使用 NoSQL 解决方案,我会推荐 Redis 或 Memcache 之类的解决方案。当然,我建议你保留一个本地缓存(为什么要承担网络调用的成本,特别是如果这些属性不太可能经常更改?)。

于 2011-05-04T11:22:51.007 回答
1

您总是可以使用 JNDI,甚至可以考虑使用 JCR 之类的文档存储库来处理这类事情。

于 2011-04-25T15:05:07.893 回答
1

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

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

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

根据您的工作流程,您将拥有一个后台进程,定期检查更新(比如说每分钟、每小时,无论对您来说都足够),或者您可以在管理“刷新本地化数据”开发人员使用时使用“按钮”需要更新。

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

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

于 2011-05-06T08:49:18.483 回答