如何(以及何时?)在 JSF 2.0 中删除 Session 范围的 bean

Posted

技术标签:

【中文标题】如何(以及何时?)在 JSF 2.0 中删除 Session 范围的 bean【英文标题】:How (and when?) to remove a Session scoped bean in JSF 2.0 【发布时间】:2012-04-02 06:17:56 【问题描述】:

背景:我有一个使用 MyFaces 的 JSF 2.0 应用程序。使用这个应用程序,用户将通过浏览许多不同的 JSF 页面来创建一个复杂的“Widget”。我将托管 bean 存储在会话范围中,并且在用户浏览每个页面时都会填写属性。一旦他们完成组装“小部件”,他们就会想要创建一个全新的“小部件”并重复该过程。我的问题是,如何在不中断 JSF 生命周期的情况下完全(并且安全地)从会话中删除托管 bean?

我看到其他类似问题的回复表明您可以使会话无效,并且您可以通过 FacesContext 访问 HttpSession 并以这种方式删除 bean。我不希望用户必须注销或重新登录,也不希望使整个会话无效。假设我需要通过 FacesContext 访问 HttpSession 并删除 bean,那么在 JSF 生命周期中何时是安全地执行此操作的最合适位置,这样问题就不会在循环的其余部分中级联?我想确保当用户开始创建下一个“小部件”的过程时,JSF 在创建新会话 bean 时不会出现问题。

我也很想知道为什么 JSF 中没有一种机制可以让这更容易。我应该采取其他一些更符合预期 JSF 模式的方法吗?我不能在这里使用 View Scope,因为托管 bean 在完成之前会经过几个不同的页面和视图。

提前致谢!

【问题讨论】:

【参考方案1】:

虽然我自己还没有使用过,但我想看看CDI的ConversationScope

【讨论】:

目前使用的是 servlet 容器,而不是完整的 J2EE 容器,但我会记住它以备将来考虑。 您可以通过Weld reference implementation将CDI添加到任何servlet容器。【参考方案2】:

如果小部件创建数据被封装到单个 bean 中,则可以简化您的情况。 我的意思是

@SessionScoped
@ManagedBean
public class WidgetMaker 
  private Widget widget;

  public void makeWidget()
   //after creating/saving widget
    widget = new Widget();
  

  public void resetWidget()
    //this method can be called with pre render view handler
    //call this method on the first page that starts widget creation
    //in case person leaves the widget creation in between and starts over again
    widget = new Widget();
  

对于视图中的 resetWidget(仅在第一个小部件创建页面中)

<f:metadata>
  <f:event type="preRenderView" listener="#widgetMaker.resetWidget"/>
</f:metadata>

您可以采取指针并相应地进行调整。

希望这会有所帮助。

【讨论】:

如果最终用户在多步骤过程中途在新的浏览器选项卡中打开小部件页面怎么办?这两个选项卡/窗口肯定会互相干扰。 @BalusC 刚刚按照描述回答,这个陷阱很明显,同意 在我的例子中,我在请求范围内有一个控制器/支持 bean,它做的事情通常不是线程安全的,比如 EntityManager 活动。会话 bean 被注入其中。使用这种设计,preRenderView 会是一个安全的地方来运行消除会话 bean 的方法吗? @tcprogrammer 你能发布一些有帮助的代码吗,但据我所知,entitymanager 应该驻留在根据 Spring 语义是单例的存储库/dao bean 中,你只需在服务中使用存储库bean 并且从未满足替换或将其放入会话范围的需要,代码示例可能会有所帮助,可能是您使用了错误的范围。 @gbagga 控制器 bean 在请求范围内,而实体/模型 bean 在会话范围内。唯一使用 EntityManager 的代码在控制器 bean 中,它在请求范围内是安全的。这不是 Spring 应用程序,它是带有本地事务的纯 JPA (EclipseLink)。我会看看我是否可以创建一个简化的示例在此处发布。【参考方案3】:

创建一个页面,您可以在其中有条件地呈现多个向导步骤。

<h:panelGroup rendered="#wizard.step == 1">
   <ui:include src="/WEB-INF/wizard/step1.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#wizard.step == 2">
   <ui:include src="/WEB-INF/wizard/step2.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#wizard.step == 3">
   <ui:include src="/WEB-INF/wizard/step3.xhtml" />
</h:panelGroup>

这样您就可以在这个单一页面上使用 @ViewScoped 托管 bean,而不会有太多麻烦。


与具体问题无关,PrimeFaces 有一个 &lt;p:wizard&gt; 组件,它的作用几乎完全一样。您可能会发现它很有用,可以让您免于验证等一些痛苦的样板代码。

【讨论】:

感谢 BalusC,我认为这可能适用于我现在的情况。在更复杂的应用程序中,尽管这可能会更成问题。处理会话 bean 真的那么麻烦吗? 我最终使用了 ViewScope bean。谢谢!【参考方案4】:

我同意 BalusC 的观点,即在您的情况下,视图范围将是更好的选择。我刚刚查看了我的(非常古老的)第一个 JSF 项目,其中我在会话范围内有一个多页表单。后来我出于众所周知的原因切换到视图状态。

但是,我确实通过简单地替换会话映射中的实例来重置会话范围 bean:

MyBean newInstance = new MyBean();
FacesContext.getCurrentInstance().getExternalContext()
   .getSessionMap().put(beanName, newInstance);

beanName 作为 bean 的 el 名称(字符串)。

【讨论】:

正如我在帖子中提到的,我知道如何访问会话映射中的 bean,但是,您在 JSF 生命周期的哪个位置执行了此操作以便安全地完成? 我是在命令按钮操作方法中完成的,从不关心这是否是正确的阶段。

以上是关于如何(以及何时?)在 JSF 2.0 中删除 Session 范围的 bean的主要内容,如果未能解决你的问题,请参考以下文章

JSF 中的“绑定”属性如何工作?何时以及如何使用它?

JSF 中的“绑定”属性如何工作?何时以及如何使用它?

JSF 2.0 动态删除组件

如何在 JSF 2.0/2.1 中用 CDI 替换 @ManagedBean / @ViewScope

JSF 2.0 在整个会话中从浏览器和以编程方式设置区域设置 [重复]

如何在 JSF 2.0 中重定向