在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?
答案 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");
可能的陷阱:
ResourceBundle
需要完全合格的类名(因此包名称)。Locale
参数,将使用默认值(即服务器)Locale
。确保在实例化Locale
时始终通过ResourceBundle
。myBundle.getString()
可以抛出MissingResourceException
。您需要使用try-catch块来避免问题。相反,如果缺少密钥(例如return "!" + key + "!"
),您可能决定从数据库访问层返回一些虚拟字符串,但无论哪种方式,它都应该记录为错误。MyBundle_pl.java
,而不是MyBundle_pl_PL.java
,它仍然有效。此外,ResourceBundle会自动回退到Enlish-US({{ 1}})如果给定语言没有资源类(这就是我使用这种奇怪的类层次结构的原因)。修改强>
关于如何使其更加 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)。