简体   繁体   English

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

[英]JSF - Resource Bundle from Spring bean and database

This is a subject that has been asked in many forums but I can't find any precise and concrete answer for it. 这是许多论坛中提出的主题,但我找不到任何准确而具体的答案。 Even the accepted answers are incomplete in my point of view, so I will try to post my complete attempt to solve this problem in hope to build a precise question + answer on the subject. 在我看来,即使是公认的答案也是不完整的,所以我会尝试发布完整的尝试来解决这个问题,希望能就这个问题建立一个精确的问题+答案。

I'm trying to get Resource Bundles working in JSF. 我正在尝试让Resource Bundles在JSF中运行。 The Resource Bundles come from a Spring bean, which should be loaded from an arbitrary external system (ie a Database). 资源包来自Spring bean,应该从任意外部系统(即数据库)加载。

I will bypass the Database querying for now and use a mocked up Resource Bundle to keep things clear. 我现在将绕过数据库查询并使用模拟的资源包来保持清晰。

This is my Resource Bundle business implementation, which I managed to gather from other post on this forum: 这是我的资源包业务实现,我设法从这个论坛的其他帖子收集:

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;
    }
}

This is a custom EL resolver that I also grabbed around in a forum. 这是一个自定义EL解析器,我也在论坛中抓住了。 It tries to gather the message if the base is an instance of MessageSource. 如果base是MessageSource的实例,它会尝试收集消息。 If not it passes the resolution to the default Spring EL resolver: 如果没有,它会将分辨率传递给默认的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();
     }

} }

The custom EL resolver is defined in faces-config.xml: 自定义EL解析器在faces-config.xml中定义:

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

Finally in Spring configuration I have the messageSource bean defined as: 最后在Spring配置中,我将messageSource bean定义为:

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

I can confirm that the messageSource bean is instantiated correctly and the HashMap is correctly loaded upon the application startup. 我可以确认messageSource bean是否正确实例化,并且在应用程序启动时正确加载了HashMap。 I can confirm that the custom handler is being called and is passing to the default Spring resolver all the EL's that are not Resource Messages and is resolving correctly. 我可以确认正在调用自定义处理程序,并且正在将所有不是资源消息的EL传递给默认的Spring解析器并正在正确解析。

When I use a Resource Bundle in a xhtml JSF 2.0 page I'm doing it this way: 当我在xhtml JSF 2.0页面中使用Resource Bundle时,我这样做:

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

During EL resolving, the custom resolver correctly detects the base as a MessageSource instance but fails at: 在EL解析期间,自定义解析程序正确地将基础检测为MessageSource实例,但在以下位置失败:

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

With the following 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)

I think that I may be missing something here, specifically in the messageSource bean definition in Spring config. 我认为我可能在这里遗漏了一些东西,特别是在Spring配置中的messageSource bean定义中。 I suspect of this because no method of TesteResBundle gets called when resolving the Resource Bundle. 我怀疑这是因为在解析资源包时没有调用TesteResBundle的方法。

Thank you for any help provided on this subject. 感谢您就此主题提供的任何帮助。

I actually managed to craft a workaround for the problem. 我实际上设法为这个问题制定了一个解决方法。 Since I'm doing my first baby-steps in Spring it would be nice if a Spring expert could review this approach as I don't think it's done in a "Spring way". 因为我正在春天做我的第一个婴儿步骤,如果Spring专家可以审查这种方法会很好,因为我不认为它是以“Spring方式”完成的。 But if everything else is failing I will stick with this not-so-beautiful workaround. 但如果其他一切都失败了,我会坚持这个不那么漂亮的解决方法。

I usually have a singleton in my web applications that holds configuration artifacts. 我的Web应用程序中通常有一个包含配置工件的单例。 Right now it's actually just holding a reference to the Spring application context: 现在它实际上只是持有对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;
    }
}

I discarded the previous Resource Bundle implementation and instead extended AbstractMessageSource: 我放弃了之前的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";
    }
}

Then I noticed that the base parameter passed to the custom EL resolver is in fact an instance of DelegatingMessageSource. 然后我注意到传递给自定义EL解析器的base参数实际上是DelegatingMessageSource的一个实例。 According to the Spring documentation: "Empty MessageSource that delegates all calls to the parent MessageSource". 根据Spring文档:“将所有调用委托给父MessageSource的空MessageSource”。 So I modified the custom EL resolver to get the Spring application context from the singleton, then get the messageResource bean and set it as the parent MessageSource to the DelegatingMessageSource instance: 所以我修改了自定义EL解析器以从单例获取Spring应用程序上下文,然后获取messageResource bean并将其设置为父MessageSource到DelegatingMessageSource实例:

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 configuration became: Spring messageSource bean配置成为:

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

Message resource access in Facelets xtml component became: Facelets xtml组件中的消息资源访问变为:

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

Where prop.aaa is passed as the "property" parameter to the custom EL resolver and we just have to lookup for that "property" and the Locale that is also passed to the resolver. 其中prop.aaa作为“属性”参数传递给自定义EL解析器,我们只需要查找该“属性”和也传递给解析器的Locale。

This way everything is working, but I'm almost certain that this can be done in a better and correct way, like passing an already configured MessageSource to the custom EL resolver. 这样一切都正常,但我几乎可以肯定这可以通过更好更正确的方式完成,比如将已经配置的MessageSource传递给自定义EL解析器。 With this I mean passing an instance of the custom MessageSource, or at least a default MessageSource having the parent already set as the custom MessageSource. 有了这个,我的意思是传递自定义MessageSource的实例,或者至少是一个默认的MessageSource,其父节点已经设置为自定义MessageSource。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM