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

发布于 2024-11-03 09:10:56 字数 580 浏览 7 评论 0原文

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

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

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

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

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

In a Java EE environment, we are normally used to storing text in a property/resource file. And that property file is associated with some view HTML markup file. E.g. if your label 'First Name' changes to 'Full Name' on a HTML page, you could use the property to make that update.

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

If you are in an environment, where it is difficult to update those property files on a regular basis, what architecture are developers using to change text/label content that would normally reside in a property file? Or let's say you need to change that content before redeploying a property file change. A bad solution is to store that in a database? Are developers using memcache? Is that usually used for caching solutions?

Edit-1 A database is really not designed for this type of task (pulling text to display on the screen), but there are use-cases for a database. I can add a locale column or state field, also add a column filter by group. If I don't use a database or property file, what distributed key/value solution would allow me to add custom filters?

Edit-2 Would you use a solution outside of the java framework? Like a key/value datastore? memcachedb?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

陈甜 2024-11-10 09:10:56

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

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

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

I want to assure you that if you need constant changes on localized texts, for example they tend to differ from deployment to deployment, database is the way to go. Well, not just the database, you need to cache your strings somehow. And of course you wouldn't want to totally re-build your resource access layer, I suppose.

For that I can suggest extending ResourceBundle class to automatically load strings from database and store it in WeakHashMap. I choose WeakHashMap because of its features - it removes a key from the map when it is no longer needed reducing memory footprint. Anyway, you need to create an accessor class. Since you mentioned J2EE, which is pretty ancient technology, I will give you Java SE 1.4 compatible example (it could be easily re-worked for newer Java, just put @Override when needed and add some String generalization to 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());
    }
}

Because of some strange ResourceBundle's loading rules, you would actually need to extend WeakResourceBundle class to create one class each for supported languages:

// 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 {

}

One supported language each (I know it sucks):

// 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");
    }
}

Now, if you need to instantiate your ResourceBundle, you would only call:

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

And to access the key:

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

Possible gotchas:

  1. ResourceBundle requires Fully Qualified Class Name (thus the package name).
  2. If you omit Locale parameter, default (which means Server) Locale would be used. Be sure to always pass Locale while instantiating ResourceBundle.
  3. myBundle.getString() could throw MissingResourceException if you follow my suggestion. You would need to use try-catch block to avoid problems. Instead you may decide on returning some dummy string from database access layer in the event of missing key (like return "!" + key + "!") but either way it should probably be logged as an error.
  4. You should always attempt to create Locale objects passing both language and country code. That is just because, languages like Chinese Simplified (zh_CN) and Chinese Traditional (zh_TW) for example, are totally different languages (at least in terms of writing) and you would need to support two flavors of them. For other countries, ResourceBundle will actually load correct language resource automatically (note that I have created MyBundle_pl.java, not MyBundle_pl_PL.java and it still works. Also, ResourceBundle would automatically fall-back to Enlish-US (MyBundle.java) if there is no resource class for given language (that is why I used such a strange class hierarchy).

EDIT

Some random thoughts about how to make it more awsome.

Static factories (avoid using ResourceBundle directly)

Instead of directly instantiating the bundles with ResourceBundle, you could add static factory method(s):

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

If you decide to change the name of WeakResourceBundle class to something more appropriate (I decided to use LocalizationProvider), you could now easily instantiate your bundles from consuming code:

ResourceBundle myBundle = LocalizationProvider.getInstance(polishLocale);

Auto-generated resource classes

Localized MyBundle classes could be easily generated via building script. The script could be either configuration file or database driven - it somehow needs to know which Locale are in use within the system. Either way, the classes share very similar code, so generating them automatically really makes sense.

Auto-detecting Locale

Since you are the one that implement the class, you have full control of its behavior. Therefore (knowing your application architecture) you can include Locale detection here and modify getInstance() to actually load appropriate language resources automatically.

Implement additional Localization-related methods

There are common tasks that needs to be done in Localized application - formatting and parsing dates, numbers, currencies, etc. are usual examples. Having end user's Locale in place, you can simply wrap such methods in LocalizationProvider.

Gee, I really love my job :)

朦胧时间 2024-11-10 09:10:56

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

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

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

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

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

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

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

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

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

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

You speak about property files, but at execution time, you are likely to have a resource bundle or something that want a list of key/value pairs (maybe even depending of the locale)

You can store data in whatever format and then use it to contruct the right ressource bundle with it. Even if it comes from memory. So database can perfectly do that, because, properties would all be loaded at startup, cached in JVM memory and that's all. (SELECT * FROM LOCALIZATION_DATA)

Don't use distributed cache for that, the data set you have is likely to be small anyway... what ? Maybe a few MB at worst. And access to that data must be instantaneous once loaded because all views will trigger access to it dozen, or even hundred of time per page.

If you want to update the data without restarting the application just add an administration screen somewhere with a "reload localization data", or even a screen that allow to update this type of data (but save to the file/DB/whatever)

From a workflow point of view, it depend of what you are trying to achieve.

The classic property file is the prefered way of doing this. You put it into versionning, together with the source code so you always have the translation up to date with the code. You want to debug V1.3.0.1 ? just get this version, and you'll use the property file that was used at this time. You added new code that require new keys ? Or just changed they key name for whatever reason ? You know that the code and your locatization information are linked into a coherant state. And this is automatic.

If your data is not under version control, you loose automatic versionning and history of your data. When you deploy/redeploy to a new machine, discrepancy can appear and even prevent the application from running propertly (if a new key is required but not added. This is not great, prone to errors and more manual interventions.

If you really need live updates, and really can't release new version for that, what i would do is to have two source for your data. The standard data, under version control, so your sure all is good for a new install from scratch. And the "customised data", in the server that can override standard values. The customized values are not lost when updating from version to version, because this is just the standard values that are updated.

If the change in the server is purely a one shoot customization, then you just go to the right admin webpage, use the customize localization data screen and that's all.

If the change is something that you'll want to keep for any new installation, you add it 2 time. One time in the server, one time in version control.

情绪失控 2024-11-10 09:10:56

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

You could always use JNDI, or even consider a document repository like JCR for this sort of thing.

失而复得 2024-11-10 09:10:56

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

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

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

Not so sure a database couldn't handle this, I think what you are really looking for is a cache that can be invalidated when those properties change. Have you thought about using something like JBoss Infinispan (http://www.jboss.org/infinispan)? It's extremely simple to use, and can be distributed across multiple application servers.

Infinispan, once configured, can be used like a Map; keep in mind you can configure it to be distributed across a cluster!

If you don't mind using a NoSQL solution, I would recommend something like Redis or Memcache. Of course, I would advocate that you keep a local cache (why incur the cost of a network call, especially if these properties are not likely to change often?).

北城半夏 2024-11-10 09:10:56

根据 Berlin Brown 的要求,我添加了另一个答案,更关注其特定需求:

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

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

根据您的工作流程,您可以有一个后台进程定期检查更新(假设每分钟、每小时,任何对您来说足够的东西),或者您可以在管理中设置一个“按钮”“刷新本地化数据”开发人员在以下情况下使用需要更新。

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

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

As requested by Berlin Brown, I add another answer, more focussed on it's specific needs :

From the amount of data you need (like a thousand of entries), you just need to load your property file at startup by specifying a remote URL.

Data is cached in JVM memory for maximum performance.

Depending on your workflow you then have a background process that check for update on a regalar basis (let say each minute, hour, whatever is enough for you) or you can have a "button" in administration "refresh localization data" developper use when an update is needed.

No need for database. No need for memcached, no need for NoSQL. a simple URL accessible from production server. In term of security and dev it is easier, faster and more flexible.

Implementation details: if you use the standard format, you'll have a file per language/contry. Don't forget to update for all languages or bundle them together (using a zip for exemple).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文