JSF 2本地化(托管bean)

问题描述:

我有一个属性文件的本地化:JSF 2本地化(托管bean)

foo=Bar 
title=Widget Application 

这是在faces-配置捆绑作为一个resource-bundle

<resource-bundle> 
    <base-name>com.example.messages.messages</base-name> 
    <var>msgs</var> 
</resource-bundle> 

我可以访问此只在使用EL的facelets视图中很好:

<title>#{msgs.title}</title> 

但是,如果有像SQLExceptions这样的东西,我需要能够从托管bean写入消息。这是所有工作也:

FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "There was an error saving this widget.", null); 
FacesContext.getCurrentInstance().addMessage(null, message); 

这里的问题:我想有这些消息都来自属性文件,这样他们也可以根据区域设置改变。有没有一种简单的方法来使用注入来访问属性文件?

我问的SO相当相关的问题: How to inject a non-serializable class (like java.util.ResourceBundle) with Weld

而且Seam的论坛里面: http://seamframework.org/Community/HowToCreateAnInjectableResourcebundleWithWeld

总结: 我意识到可注射的ResourceBundle 3生产者。 首先你需要一个FacesContextProducer。我从Seam 3 Alpha来源获取了一个。

public class FacesContextProducer { 
    @Produces @RequestScoped 
    public FacesContext getFacesContext() { 
     FacesContext ctx = FacesContext.getCurrentInstance(); 
     if (ctx == null) 
     throw new ContextNotActiveException("FacesContext is not active"); 
     return ctx; 
    } 
} 

然后,您需要一个LocaleProducer,它使用FacesContextProducer。我也从Seam 3 Alpha拿下了它。

public class FacesLocaleResolver { 
    @Inject 
    FacesContext facesContext; 

    public boolean isActive() { 
     return (facesContext != null) && (facesContext.getCurrentPhaseId() != null); 
    } 

    @Produces @Faces 
    public Locale getLocale() { 
     if (facesContext.getViewRoot() != null) 
     return facesContext.getViewRoot().getLocale(); 
     else 
     return facesContext.getApplication().getViewHandler().calculateLocale(facesContext); 
    } 
} 

现在你拥有了一切创造ResourceBundleProducer,它可以是这样的:

public class ResourceBundleProducer { 
    @Inject  
    public Locale locale; 

    @Inject  
    public FacesContext facesContext; 

    @Produces 
    public ResourceBundle getResourceBundle() { 
    return ResourceBundle.getBundle("/messages", facesContext.getViewRoot().getLocale()); 
    } 
} 

现在你可以@Inject资源包进入你的豆类。注意它必须注入一个transient属性,否则你会得到一个异常,抱怨ResourceBundle不可序列化。

@Named 
public class MyBean { 
    @Inject 
    private transient ResourceBundle bundle; 

    public void testMethod() { 
    bundle.getString("SPECIFIC_BUNDLE_KEY"); 
    } 
} 

下面是关于如何做到这一点的例子: http://www.laliluna.de/articles/javaserver-faces-message-resource-bundle-tutorial.html

你想看看在ResourceBundle.getBundle()一部分。

问候, 拉尔斯

+0

我看到了这个,当我GOOGLE了它。然而,有没有一种更优雅的方式让容器使用@Resource(“#{msgs}”)或类似的东西来注入?我想,因为我使用的是CDI,所以我可以创建一个'@ MessageBundle'的生产者,然后传回一个'Properties'对象... – 2010-08-13 15:15:08

+0

我在最后一个项目中使用了这种方法 - 我们有DB错误的相同问题。如果这个问题仍然没有解决,我可以在下周三看看这个老源。 – Lars 2010-08-13 15:27:05

+0

你是对的,这是一个有效的方法。我只是想知道是否有办法更优雅地做到这一点。我可以使用CDI来注入它。如果没有内置的注释,这将起作用。 – 2010-08-13 15:44:10

它更容易使用,例如,消息模块MyFaces CODI

您可以单独使用JSF来执行此操作。

首先在您的支持bean上定义托管属性。在JSF配置中,可以将托管属性的值设置为引用资源包的EL表达式。

我使用Tomcat 6做了类似如下的事情。唯一需要注意的是,您无法从支持bean的构造函数中访问此值,因为JSF尚未初始化它。如果在bean的生命周期中早期需要该值,则在初始化方法上使用@PostConstruct

<managed-bean> 
    ... 
    <managed-property> 
    <property-name>messages</property-name> 
    <property-class>java.util.ResourceBundle</property-class> 
    <value>#{msgs}</value> 
    </managed-property> 
    ... 
</managed-bean> 

<application> 
    ... 
    <resource-bundle> 
    <base-name>com.example.messages.messages</base-name> 
    <var>msgs</var> 
    </resource-bundle> 
    ... 
</application> 

这样做的好处是使您的支持bean方法更少地依赖于演示技术,因此它应该更容易测试。它还将您的代码从类似于捆绑软件的名称等细节中分离出来。

使用Mojarra 2.0.4-b09进行的一些测试在用户更改区域设置中会话时确实显示出小的不一致性。页面内的EL表达式使用新的区域设置,但是辅助bean没有被赋予新的ResourceBundle引用。为了保持一致,您可以在EL表达式中使用bean属性值,例如使用#{backingBean.messages.greeting}代替#{msgs.greeting}。然后页面EL和辅助bean将始终使用会话开始时处于活动状态的语言环境。如果用户在会话中间切换语言环境并获取新消息,则可以尝试制作请求范围的bean,并为会话bean和资源包提供引用。

+0

+1到目前为止最简单的解决方案 – klonq 2012-02-29 09:54:17

+2

在JSF2中,您可以使用'@ManagedProperty(“#{msgs}”)private ResourceBundle msgs;'(with setter)。 – BalusC 2012-09-05 11:35:25

这是一个老问题,但我增加了另一种方式来做到这一点。我正在寻找别的东西,并且碰到了这个。这里的方法似乎令人费解,因为所有那些困难都没有引起我的注意。如果你问我的话,就会闪闪发光,因为它很漂亮。

给定一个文件:

/com/full/package/path/to/messages/errormessages.properties 

里面的档案:

SOME_ERROR_STRING=Your App Just Cratered 

我创建了一个“的getBundle()”方法,因为我喜欢抓运行,并添加一个有意义的消息,所以我会了解它来自哪里。不难,如果您因为某种原因而随意使用属性文件并且没有正确更新所有内容,可以提供帮助。我有时会把它变成私人的,因为它有时是班级内的辅助方法(对我来说)。这样可以避免有意义代码中的try catch混乱。

如果您有关于组织的其他想法,使用文件的完整路径允许您将它放在默认位置/目录以外的某个位置。

public/private ResourceBundle getMessageResourceBundle(){ 
    String messageBundle = "com.full.package.path.to.messages.errormessages"; 
    ResourceBundle bundle = null; 
    try{ 
     bundle = ResourceBundle.getBundle(messageBundle); 
    }catch(MissingResourceException ex){ 
     Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, 
        "Unable to find message bundle in XYZ Class", ex); 
     throw ex; 
    } 

} 

public void doSomethingWithBundle(){ 

    ResourceBundle bundle = getMessageResourceBundle(); 
    String someString = bundle.getString("SOME_ERROR_STRING"); 
    ... 
}