spring - JSF - 来自 Spring bean 和数据库的资源包

标签 spring jsf spring-mvc jsf-2 resourcebundle

这是一个在许多论坛中被问到的主题,但我找不到任何准确和具体的答案。在我看来,即使是公认的答案也不完整,所以我会尝试发布我解决这个问题的完整尝试,希望能就这个问题建立一个精确的问答。

我正在尝试让 Resource Bundle 在 JSF 中工作。资源包来自 Spring bean,它应该从任意外部系统(即数据库)加载。

我现在将绕过数据库查询并使用模拟的资源包来保持清晰。

这是我的 Resource Bundle 业务实现,我设法从该论坛的其他帖子中收集到:

public class TesteResBundle extends ReloadableResourceBundleMessageSource {

    private final Map<String, Map<String, String>> properties = new HashMap<String, Map<String, String>>();

    public TesteResBundle() {
        reload();
    }

    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        String msg = getText(code, locale);
        MessageFormat result = createMessageFormat(msg, locale);
        return result;
    }

    @Override
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        return getText(code, locale);
    }

    private String getText(String code, Locale locale) {
        Map<String, String> localized = properties.get(code);
        String textForCurrentLanguage = null;
        if (localized != null) {
            textForCurrentLanguage = localized.get(locale.getLanguage());
            if (textForCurrentLanguage == null) {
                textForCurrentLanguage = localized.get(Locale.ENGLISH.getLanguage());
            }
        }
        return textForCurrentLanguage != null ? textForCurrentLanguage : code;
    }

    public void reload() {
        properties.clear();
        properties.putAll(loadTexts());
    }

    protected Map<String, Map<String, String>> loadTexts() {

        Map<String, Map<String, String>> m = new HashMap<String, Map<String, String>>();
        Map<String, String> v = new HashMap<String, String>();
        v.put("en", "good");
        v.put("pt", "bom");
        v.put("en_US", "bom");
        m.put("prop", v);

        v = new HashMap<String, String>();
        v.put("en", "bad");
        v.put("pt", "mau");
        v.put("en_US", "bom");
        m.put("pror", v);
        return m;
    }
}

这是一个自定义的 EL 解析器,我也在论坛上找到了它。如果 base 是 MessageSource 的实例,它会尝试收集消息。如果不是,它将解析传递给默认的 Spring EL 解析器:

public class MessageSourcePropertyResolver extends SpringBeanFacesELResolver {

    public Object getValue(ELContext elContext, Object base, Object property)
        throws ELException {

        if (base instanceof MessageSource && property instanceof String) {
            String result = ((MessageSource) base).getMessage(
                (String) property, null, getLocale());

            if (null != result) {
                elContext.setPropertyResolved(true);
            }

            return result;
        }

        return super.getValue(elContext, base, property);
    }

     private Locale getLocale() {
        FacesContext context = FacesContext.getCurrentInstance();
        return context.getExternalContext().getRequestLocale();
     }

自定义 EL 解析器在 faces-config.xml 中定义:

<el-resolver>pt.teste.pojo.MessageSourcePropertyResolver</el-resolver>

最后在 Spring 配置中,我将 messageSource bean 定义为:

<bean id="messageSource" class="pt.teste.pojo.TesteResBundle">
</bean>

我可以确认 messageSource bean 已正确实例化,并且 HashMap 在应用程序启动时已正确加载。我可以确认正在调用自定义处理程序并将所有不是资源消息的 EL 传递给默认的 Spring 解析器并且正在正确解析。

当我在 xhtml JSF 2.0 页面中使用资源包时,我是这样做的:

<h:outputText value="#{messageSource.prop}" />

在 EL 解析期间,自定义解析器正确地将基检测为 MessageSource 实例,但在以下位置失败:

String result = ((MessageSource) base).getMessage((String) property, null, getLocale());

有以下异常(exception):

org.springframework.context.NoSuchMessageException: No message found under code 'prop' for locale 'en_US'.
org.springframework.context.support.DelegatingMessageSource.getMessage(DelegatingMessageSource.java:65)
pt.teste.pojo.MessageSourcePropertyResolver.getValue(MessageSourcePropertyResolver.java:18)
com.sun.faces.el.DemuxCompositeELResolver._getValue(DemuxCompositeELResolver.java:176)
com.sun.faces.el.DemuxCompositeELResolver.getValue(DemuxCompositeELResolver.java:203)
org.apache.el.parser.AstValue.getValue(AstValue.java:169)
org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:189)
com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:109)
javax.faces.component.ComponentStateHelper.eval(ComponentStateHelper.java:194)
javax.faces.component.ComponentStateHelper.eval(ComponentStateHelper.java:182)
javax.faces.component.UIOutput.getValue(UIOutput.java:169)
com.sun.faces.renderkit.html_basic.HtmlBasicInputRenderer.getValue(HtmlBasicInputRenderer.java:205)
com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.getCurrentValue(HtmlBasicRenderer.java:355)
com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeEnd(HtmlBasicRenderer.java:164)
javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:875)
com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeRecursive(HtmlBasicRenderer.java:312)
com.sun.faces.renderkit.html_basic.GroupRenderer.encodeChildren(GroupRenderer.java:105)
javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:845)
javax.faces.component.UIComponent.encodeAll(UIComponent.java:1779)
com.sun.faces.renderkit.html_basic.CompositeRenderer.encodeChildren(CompositeRenderer.java:78)
javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:845)
javax.faces.component.UIComponent.encodeAll(UIComponent.java:1779)
javax.faces.render.Renderer.encodeChildren(Renderer.java:168)
javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:845)
javax.faces.component.UIComponent.encodeAll(UIComponent.java:1779)
javax.faces.component.UIComponent.encodeAll(UIComponent.java:1782)
com.sun.faces.application.view.FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java:402)
com.sun.faces.application.view.MultiViewHandler.renderView(MultiViewHandler.java:125)
org.springframework.faces.webflow.FlowViewHandler.renderView(FlowViewHandler.java:99)
com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:121)
com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
org.springframework.faces.mvc.JsfView.renderMergedOutputModel(JsfView.java:85)
org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:262)
org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1180)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:950)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
javax.servlet.http.HttpServlet.service(HttpServlet.java:734)
javax.servlet.http.HttpServlet.service(HttpServlet.java:847)

我认为我可能在这里遗漏了一些东西,特别是在 Spring 配置中的 messageSource bean 定义中。我怀疑这是因为在解析资源包时没有调用 TesteResBundle 的方法。

感谢您就此主题提供的任何帮助。

最佳答案

我实际上设法为这个问题设计了一个解决方法。由于我是在 Spring 中做我的第一个婴儿步骤,如果 Spring 专家可以审查这种方法会很好,因为我认为它不是以“Spring 方式”完成的。但如果其他一切都失败了,我会坚持使用这个不太漂亮的解决方法。

我的 Web 应用程序中通常有一个单例来保存配置工件。现在它实际上只是持有对 Spring 应用程序上下文的引用:

public class ApplicationConfig {

    private static ApplicationConfig instance = new ApplicationConfig();

    private ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

    private ApplicationConfig(){

    }

    public static ApplicationConfig instance(){
        return instance;
    }

    public ApplicationContext getApplicationContext(){
        return context;
    }
}

我放弃了以前的 Resource Bundle 实现,而是扩展了 AbstractMessageSource:

public class TesteMessageSource extends AbstractMessageSource {

    @Override
    protected MessageFormat resolveCode(String key, Locale locale) {
        // This is just a dummy method.
        // It should lookup in a Map created in this same 
        // class for the correct key/locale resource value
        return createMessageFormat(key, Locale.US);
    }

    @Override
    protected String resolveCodeWithoutArguments(String key, Locale locale){
        // This is just a dummy method.
        // It should lookup in a Map created in this same 
        // class for the correct key/locale resource value
        return "dummyString";
    }
}

然后我注意到传递给自定义 EL 解析器的基本参数实际上是 DelegatingMessageSource 的一个实例。根据 Spring 文档:“将所有调用委托(delegate)给父 MessageSource 的空 MessageSource”。所以我修改了自定义 EL 解析器以从单例中获取 Spring 应用程序上下文,然后获取 messageResource bean 并将其设置为 DelegatingMessageSource 实例的父 MessageSource:

public class MessageSourcePropertyResolver extends SpringBeanFacesELResolver /*implements MessageSourceAware */{

    public Object getValue(ELContext elContext, Object base, Object property)
        throws ELException {

        if (base instanceof MessageSource && property instanceof String) {
            DelegatingMessageSource delegatingMessageSource = (DelegatingMessageSource) base;
            BeanFactory factory = ApplicationConfig.instance().getApplicationContext();
            MessageSource messageSource = (MessageSource) factory.getBean("messageSource");
            delegatingMessageSource.setParentMessageSource(messageSource);

            String result = delegatingMessageSource.getMessage((String) property, new Object[] {}, getLocale());
            if (result != null) {
                elContext.setPropertyResolved(true);
            }

            return result;
        }

        return super.getValue(elContext, base, property);
    }

    private Locale getLocale() {
        FacesContext context = FacesContext.getCurrentInstance();
        return context.getExternalContext().getRequestLocale();
    }
}

Spring messageSource bean 配置变为:

<bean id="messageSource" class="pt.teste.pojo.TesteMessageSource">
</bean>

Facelets xml 组件中的消息资源访问变为:

<h:outputText value="${messageSource['prop.aaa']}" />

其中 prop.aaa 作为“属性”参数传递给自定义 EL 解析器,我们只需要查找该“属性”和也传递给解析器的区域设置。

这样一切正常,但我几乎可以肯定这可以以更好和正确的方式完成,例如将已配置的 MessageSource 传递给自定义 EL 解析器。我的意思是传递一个自定义 MessageSource 的实例,或者至少传递一个默认的 MessageSource,其父级已设置为自定义 MessageSource。

关于spring - JSF - 来自 Spring bean 和数据库的资源包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12309754/

相关文章:

javascript - Thymeleaf 页面刷新跟进 - 现在使用 AJAX

java - ClassNotFoundException 与 Spring JavaConfigWebApplicationContext

java - 重复调用Destroy和New Context是不是不好?

java - 在 JSF 中使用 Facades 模式,仅在一个实体中,实体管理器为 Null

java - JUnit 测试(使用 Spring MVC 和 Hibernate): IllegalArgumentException: Unknown entity

spring-mvc - Spring MVC 的 @Async、DeferredResult 和 Callable 的区别

java - 内部bean的范围

spring - refreshToken 端点实现在哪里?

javascript - 在 JSF 中 - 获取客户端的语言环境(以浏览器的时区显示时间)

css - JSF - 过滤文本框在 IE 中的出现