使用MyFaces Orchestra时,在导航到其他视图时不会删除conversation.access bean

时间:2011-06-28 18:40:46

标签: jsf-2 myfaces orchestra

我们正在使用JSF 2,Spring和Hibernate构建应用程序。 MyFaces Orchestra习惯于提供我们用于应用程序中大多数页面的会话范围(以利用Orchestra对Hibernate会话的管理)。我们所有的bean都声明使用conversation.access作用域(根据Orchestra文档)应该意味着一旦用户导航到不包含对该支持bean实例的任何引用的页面,就会从作用域中删除bean

我遇到的问题是,如果我从一个视图中导航而没有明确地使对话无效,如果他们稍后回到该视图,它仍然具有与之前相同的数据。我在所有支持bean中实现了ConversationBindingListener方法,我可以看到它们何时从对话中删除,我可以看到它们在很多情况下都不存在。

使问题更令人困惑的是,当我导航到某些页面(视图)而不是其他页面时,会删除支持bean。我想也许那是因为页面有一个无意识的引用EL中的其他支持bean但我找不到任何。我还认为,这个问题可能只发生在我从一个具有conversation.access scoped bean的页面导航到另一个使用不同的conversation.scoped bean的页面时。但是,从对话中删除它的情况,该页面还包含对conversation.access范围bean的引用。

正如我早些时候所说,使用Conversation.getCurrentInstance()显式地使对话无效.invalidate()有效。但是,对于每个用例都不能明确地使对话无效,因为这将是一个非常常见的用例,用户可以通过单击其中一个导航链接离开视图。

其他细节: 我们使用的是Hibernate 3.6(而不是JPA),这意味着我们必须使用HibernatePersistenceContextFactory

  • MyFaces Orchestra (MyFaces的乐团-core20-1.4.jar)
  • JSF 2(Mojarra 2.0.4)
  • Spring 3.0
  • PrimeFaces 2.2.1
  • RichFaces 4.0.0

以下是我的Spring上下文配置(适用于Orchestra)。

<!-- 1. initialization of all orchestra modules (required for core15 module) -->
<import resource="classpath*:/META-INF/spring-orchestra-init.xml" />

<!-- 2. the conversation scopes -->
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="conversation.manual">
                <bean
                    class="org.apache.myfaces.orchestra.conversation.spring.SpringConversationScope">
                    <property name="timeout" value="30" />
                    <property name="advices">
                        <list>
                            <ref bean="persistentContextConversationInterceptor" />
                        </list>
                    </property>
                </bean>
            </entry>
            <entry key="conversation.access">
                <bean
                    class="org.apache.myfaces.orchestra.conversation.spring.SpringConversationScope">
                    <property name="timeout" value="30" />
                    <property name="advices">
                        <list>
                            <ref bean="persistentContextConversationInterceptor" />
                        </list>
                    </property>
                    <property name="lifetime" value="access" />
                </bean>
            </entry>
        </map>
    </property>
 </bean>    


<!-- 3. the "entity manager" manager -->
<bean id="persistentContextConversationInterceptor"
    class="org.apache.myfaces.orchestra.conversation.spring.PersistenceContextConversationInterceptor">
    <property name="persistenceContextFactory" ref="persistentContextFactory" />
</bean>



<!-- 4. conversation - persistence adapter -->
<bean id="persistentContextFactory"
    class="com.acme.infra.orchestra.hibernate.HibernatePersistenceContextFactory">
    <property name="entityManagerFactory" ref="sessionFactory" />
</bean>

<!-- 5. persistence -->
<bean id="managedDataSource"
    class="org.apache.myfaces.orchestra.connectionManager.ConnectionManagerDataSource">
    <property name="dataSource" ref="dataSource" />
</bean>

以下是JSF支持bean声明的几个示例。

<bean id="quoteSummaryBackingBean" class="com.acme.ui.backing.QuoteSummaryBackingBean"
        scope="conversation.access" orchestra:conversationName="QuoteSummaryConversation">
    <property name="quotingBusinessService" ref="quotingBusinessService"/>
    <property name="customerBusinessService" ref="customerBusinessService"/>
    <property name="referenceDataBusinessService" ref="referenceDataBusinessService"/>
    <property name="quoteExportBusinessService" ref="quoteExportBusinessService" />
</bean>

<bean id="createQuoteBackingBean" class="com.acme.ui.backing.CreateQuoteBackingBean" 
        scope="conversation.access" orchestra:conversationName="CreateQuoteConversation">  
    <property name="quotingBusinessService" ref="quotingBusinessService"/>
    <property name="customerBusinessService" ref="customerBusinessService"/>
    <property name="referenceDataBusinessService" ref="referenceDataBusinessService"/>


        

2 个答案:

答案 0 :(得分:2)

这不是最优雅的解决方案,我猜它可能会引入新的错误(因为Orchestra使用的检查旨在处理AJAX请求的情况)。我向我的后台bean(使用基类)添加了一个新方法,该方法处理将org.apache.myfaces.orchestra.conversation.jsf.AccessScopePhaseListener:oldView请求范围变量重置为null。

public void clearPreviousConversation() {
    if (firstHit) {
        String keyName = 
            "org.apache.myfaces.orchestra.conversation.jsf.AccessScopePhaseListener:oldView";

        FacesContext.getCurrentInstance().getExternalContext()
        .getRequestMap().put(keyName, null);

        firstHit = false;
    }
}   

为了确保每个视图只调用一次此方法,我有一个“firstHit”标志,它是一个布尔成员变量。

然后,由于此特定问题仅在使用f:metadata的视图上显示,因此我利用该事实仅在需要时调用此方法。我将它添加为我的f:metadata中的预渲染调用。

<f:metadata>
    <f:event type="preRenderView" listener="#{controlPanelBackingBean.clearPreviousConversation}" />
</f:metadata>

如果你正在使用f:viewParam或其他f:event元素,你可以将它们混合在一起。

<f:metadata>
    <f:viewParam name="tabIndex" value="#{controlBackingBean.tabIndex}" />
    <f:event type="preRenderView" listener="#{controlPanelBackingBean.clearPreviousConversation}" />
    <f:event type="preRenderView" listener="#{controlPanelBackingBean.init}" />
</f:metadata>

答案 1 :(得分:0)

正如Benito Vega在评论中正确地说的那样,当您对包含<f:metadata/>标记的视图发出GET请求时,问题就会出现。这是因为Orchestra的AccessScopePhaseListener.doAfterRestoreView()通过测试FacesContext.getRenderResponse()来区分POST和GET。但是,对于包含true标记的视图的GET请求,这不是<f:metadata/>(请参阅RestoreViewPhase.java的第244行,了解其原因)。这就是为什么这个案例会查看AccessScopePhaseListener.doAfterRenderResponse()中的后续代码,就像对相同视图的回发一样,这是跳过未访问bean失效的原因。

我已经创建了自己的阶段监听器来解决这个问题。它添加了AccessScopePhaseListener.doAfterRestoreView()一个片段的结果,该片段使得'oldView'请求属性的状态对于任何GET请求都是相同的,无论视图是否包含<f:metadata/>。该片段在RENDER_RESPONSE阶段之前运行,因此管弦乐队的听众和矿山的相互顺序并不重要。

import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import org.apache.myfaces.orchestra.conversation.jsf.AccessScopePhaseListener;

public class OrchestraAccessScopeBugFixer implements PhaseListener {
    /**
     * @see AccessScopePhaseListener#OLD_VIEW_KEY
     */
    private static final String OLD_VIEW_KEY = AccessScopePhaseListener.class.getName() + ":oldView";

    @Override
    public void beforePhase(PhaseEvent event) {
        FacesContext facesContext = event.getFacesContext();
        if (!facesContext.isPostback()) {
            // this makes it think that we are on a new view, not posting back to the same one
            facesContext.getExternalContext().getRequestMap().put(OLD_VIEW_KEY, null);
        }
    }

    @Override
    public void afterPhase(PhaseEvent event) {
    }

    @Override
    public PhaseId getPhaseId() {
        return PhaseId.RENDER_RESPONSE;
    }
}

我已经为GET测试了这个解决方案,无论是否有<f:metadata/>,对于同一视图的POST和POST导航到另一个视图,它都按预期工作。然而,我不确定为什么Orchestra开发人员无法在FacesContext.isPostback()中使用FacesContext.getRenderResponse()代替AccessScopePhaseListener.doAfterRestoreView(),以便在回发和非回复之间有所区别。