使用 MyFaces Orchestra 时,导航到不同视图时对话.access beans 不会被删除
我们正在使用 JSF 2、Spring 和 Hibernate 构建一个应用程序。 MyFaces Orchestra 用于提供我们在应用程序中的大多数页面中使用的对话范围(以利用 Orchestra 对 Hibernate 会话的管理)。我们所有的 bean 都被声明为使用 communications.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-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"/>
We are building an application using JSF 2, Spring, and Hibernate. MyFaces Orchestra is being used to to provide conversation scope which we're using for most of the pages in the application (to take advantage of Orchestra's management of the Hibernate Session). All of our beans are declared to use the conversation.access scope which (according to the Orchestra documentation) should mean that the beans are removed from scope as soon as the user navigates to a page that does not contain any references to that backing bean instance.
The problem I'm encountering is that, if I navigate a way from a view without explicitly invalidating the conversation, if they come back to that view later it still has the same data as before. I implemented ConversationBindingListener methods in all my backing beans and I can see when they're being removed from the conversation and I can see that they're NOT in many cases.
What makes the issue more perplexing is that the backing beans are removed when I navigate to some pages (views) but not to others. I thought maybe that was because pages had an unintended reference to other backing beans in the EL but I was not able find any. I also thought that maybe this issue only happened when I navigated from one page that had a conversation.access scoped bean to another page using a different conversation.scoped bean. However, the cases where it is removed from the conversation, that page also contains references to a conversation.access scoped bean.
As I said early, explicitly invalidating the conversation using Conversation.getCurrentInstance().invalidate() works. However, explicitly invalidating the conversation is not possible for every use case since it will be a very common use case for the user can leave a view just by clicking on one of the navigation links.
ADDITIONAL DETAILS:
We're using Hibernate 3.6 (instead of JPA) so that meant we had to use the HibernatePersistenceContextFactory.
- MyFaces Orchestra
(myfaces-orchestra-core20-1.4.jar) - JSF 2 (Mojarra 2.0.4)
- Spring 3.0
- PrimeFaces 2.2.1
- RichFaces 4.0.0
Here is what my Spring context configuration looks like (for 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>
Here are a couple examples of JSF backing bean declarations.
<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"/>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这不是最优雅的解决方案,我猜测它可能会引入新的错误(因为 Orchestra 使用的检查旨在处理 AJAX 请求的情况)。我向支持 bean 添加了一个新方法(使用基类),用于将 org.apache.myfaces.orchestra.conversation.jsf.AccessScopePhaseListener:oldView 请求范围变量重置为 null。
为了确保每个视图只调用此方法一次,我有“firstHit”标志,它是一个布尔成员变量。
然后,由于这个特定问题仅在使用 f:metadata 的视图上显现出来,因此我利用这一事实仅在需要时调用此方法。我将其添加为 f:metadata 中的预渲染调用。
如果您使用 f:viewParam 或其他 f:event 元素,则可以将它们混合在一起。
This is not the most elegant solution and I'm guessing it could be introducing new bugs (since the check used by Orchestra was meant to handle situations with AJAX requests). I added a new method to my backing beans (using a base class) that handles resetting the org.apache.myfaces.orchestra.conversation.jsf.AccessScopePhaseListener:oldView request scope variable to null.
To ensure this method only gets called one time per view, I have the "firstHit" flag which is a boolean member variable.
Then, since this particular issue only manifests itself on views that use f:metadata, I take advantage of that fact to only call this method where it's needed. I add it as a pre-render call in my f:metadata.
If you're using f:viewParam or other f:event elements, you can mix them together.
正如 Benito Vega 在他的评论中正确指出的那样,当您对包含
标记的视图发出 GET 请求时,问题就会显现出来。这是因为 Orchestra 的AccessScopePhaseListener.doAfterRestoreView()
通过测试FacesContext.getRenderResponse()
来区分 POST 和 GET。但是,如果对包含
标记的视图发出 GET 请求,则情况并非如此(参见 RestoreViewPhase.java 了解为什么会这样)。这就是为什么此案例将AccessScopePhaseListener.doAfterRenderResponse()
中的后续代码视为回发到同一视图,这是跳过未访问的 bean 失效的原因。我创建了自己的阶段监听器来解决这个问题。它向
AccessScopePhaseListener.doAfterRestoreView()
的结果添加了一个代码片段,该代码片段使得“oldView”请求属性的状态对于任何 GET 请求都显得相同,无论视图是否包含
或不。该代码片段在RENDER_RESPONSE
阶段之前运行,因此 Orchestra 听众和我的听众的相互顺序并不重要。我已经测试了此解决方案,用于 GET 到带有或不带有
的视图、POST 到同一视图以及 POST 导航到另一个视图,并且它按预期工作。但我不确定为什么 Orchestra 开发人员不能在AccessScopePhaseListener.doAfterRestoreView()
中使用FacesContext.isPostback()
而不是FacesContext.getRenderResponse()
为了区分回发和非回发。As Benito Vega correctly said in his comment, the problem manifests when you do a GET request to a view that contains
<f:metadata/>
tag. This is because Orchestra'sAccessScopePhaseListener.doAfterRestoreView()
differentiates between POST and GET by testingFacesContext.getRenderResponse()
. However this is nottrue
in case of a GET request to a view containing an<f:metadata/>
tag (see line 244 of RestoreViewPhase.java for why it is so). That's why this case looks to the subsequent code inAccessScopePhaseListener.doAfterRenderResponse()
like a postback to the same view, which is a reason for skipping invalidation of not accessed beans.I've created my own phase listener to fix this problem. It adds to the result of
AccessScopePhaseListener.doAfterRestoreView()
a snippet which makes the state of 'oldView' request attribute look equally for any GET request regardless of whether the view contains<f:metadata/>
or not. The snippet runs beforeRENDER_RESPONSE
phase, so the mutual order of the Orchestra's listener and the mine is not important.I've tested this solution for GET to a view with or without
<f:metadata/>
, for POST to the same view and for POST with navigation to another one, and it's working as expected. Yet I am not sure why Orchestra developers could not useFacesContext.isPostback()
instead ofFacesContext.getRenderResponse()
inAccessScopePhaseListener.doAfterRestoreView()
in order to make difference between a postback and a not one.