我们正在使用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。
以下是我的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"/>
答案 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()
,以便在回发和非回复之间有所区别。