3

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

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

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

正如我之前所说,使用 Conversation.getCurrentInstance().invalidate() 显式地使对话无效是有效的。但是,不可能对每个用例都显式地使对话无效,因为这将是一个非常常见的用例,因为用户只需单击其中一个导航链接即可离开视图。

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

  • MyFaces Orchestra (myfaces-orchestra-core20-1.4.jar)
  • JSF 2 (Mojarra 2.0.4)
  • 春天 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"/>


4

2 回答 2

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>
于 2011-07-07T03:49:50.600 回答
0

正如 Benito Vega 在他的评论中正确所说的那样,当您对包含<f:metadata/>标签的视图执行 GET 请求时,问题就会显现出来。这是因为 OrchestraAccessScopePhaseListener.doAfterRestoreView()通过测试区分 POST 和 GET FacesContext.getRenderResponse()true但是,对于包含<f:metadata/>标签的视图的 GET 请求,情况并非如此(请参阅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()inAccessScopePhaseListener.doAfterRestoreView()来区分回发和非回发。

于 2013-06-03T16:03:53.753 回答