JSF 2本地化(托管Bean)

7

我有一个用于本地化的属性文件:

foo=Bar
title=Widget Application

这是在 faces-config 中作为“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);

问题在于:我希望那些消息来自属性文件,这样它们也可以根据语言环境进行更改。是否有一种简单的方法使用注入访问属性文件?

5个回答

17

我在SO上问了一个相关的问题: 如何使用Weld注入一个不可序列化的类(如java.util.ResourceBundle)

在Seam论坛上也有相关讨论: http://seamframework.org/Community/HowToCreateAnInjectableResourcebundleWithWeld

总结一下: 我使用3个生产者实现了可注入的ResourceBundle。 首先,你需要一个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() );
  }
}

现在您可以将ResourceBundle @Inject到您的bean中。请注意,它必须注入到一个瞬态属性中,否则您将收到一个异常,指出ResourceBundle不可序列化。

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

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

2
您可以仅使用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属性值,例如在#{msgs.greeting}的位置使用#{backingBean.messages.greeting}。然后页面EL和后备Bean将始终使用会话开始时处于活动状态的语言环境。如果用户必须在会话期间切换语言环境并获得新消息,则可以尝试创建一个请求范围的Bean,并为其提供对会话Bean和资源包的引用。


2
在JSF2中,您只需使用@ManagedProperty("#{msgs}") private ResourceBundle msgs;(带有setter)。 - BalusC

2

0

这是一个老问题,但我想提供另一种解决方法。我本来在寻找其他东西时偶然发现了这个问题。这里的方法似乎都很复杂,而实际上并不难。如果你问我,我只是因为它很漂亮才玩闪光粉。

给定一个文件:

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

但是消息被检索时没有考虑区域设置。 - Galilo Galilo

0

我在谷歌上搜索时看到了这个。但是,有没有更优雅的方法让容器使用@Resource ("#{msgs}")或类似的东西注入这个?我想,由于我正在使用CDI,我可以创建一个@MessageBundle的producer,然后只需返回一个Properties对象... - Zack Marrapese
我在我们最近的一个项目中使用了这种方法 - 我们遇到了与数据库错误相同的问题。如果该问题仍未解决,我可以在下周三查看旧源代码。 - Lars
你说得对,这是一种有效的方法。我只是想知道是否有更优雅的方法来实现它。我可以使用CDI进行注入。如果没有内置的注释,那么这将起作用。 - Zack Marrapese

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接