【问题标题】:@PostConstruct called multiple time for @ConversationScoped bean@PostConstruct 为 @ConversationScoped bean 调用了多次
【发布时间】:2011-05-28 12:22:51
【问题描述】:

我有一个 @ConversationScoped bean,带有一个 start 方法,如下所示:

@PostConstruct
public void start() {
    if (conversation.isTransient()) {
        conversation.begin();
        log.debug("conversation.getId(): " + conversation.getId());
    }
}

我的问题是每次刷新页面时都会启动一个新对话,每次我对 bean 中的方法进行 AJAX 调用时也会启动一个新对话(这是我的主要问题)。

我真正想要的是让 sam 对话一直存在,直到我手动调用对话.end()。我在这里错过了什么?

【问题讨论】:

    标签: java java-ee-6 cdi jboss-weld seam-conversation


    【解决方案1】:

    有点离题,但希望有价值:

    我不能 100% 确定 @PostConstruct 是开始对话的正确位置。我宁愿使用这样的面孔事件:

    <f:metadata>
            <f:event type="javax.faces.event.PreRenderViewEvent"
                    listener="#{myBean.init}" />
    </f:metadata>
    

    如果您确定自己不在 JSF 回发请求中,则开始对话。

    public void init() {
           if (!FacesContext.getCurrentInstance().isPostback() && conversation.isTransient()) {
              conversation.begin();
           }
        }
    

    如果你使用 Seam 3,那就更简单了:

    <f:metadata>
       <s:viewAction action="#{myBean.init}" if="#{conversation.transient}" />
    </f:metadata>
    

    【讨论】:

    • 我同意,如果我刷新页面,我真的不希望开始新的对话。事实上,如果我为对话命名并刷新页面,我会得到一个异常,因为它不能重复使用相同的对话 ID。如果您可以像 Seam 2 那样加入对话,那就太好了。我想您的方法会遇到同样的问题,但我确实喜欢您的方法而不是使用 @PostConstruct。
    • 我刚试过,它适用于 AJAX 请求,但如果我刷新页面,我会得到“WELD-000218 对话 ID myConversation 已在使用中”。所以那里有50%:)。我知道为什么它会在刷新时发生,我只是希望我可以让它在刷新时重用当前对话。
    【解决方案2】:

    您是否检查过 (AJAX) 调用是否包含会话 ID 参数 (cid)?

    如果没有,每次通话都会开始一个新的对话。

    【讨论】:

    • 我正在确保我的请求中包含 CID,但我错过了一个请求,因此它正在开始新的对话。所以这解决了一半的问题,谢谢:)。当我真的希望它使用当前对话时,我仍然遇到页面刷新开始新对话的问题。不确定如何/是否可能。
    • 您想在浏览器地址栏中按回车键刷新页面时重复使用对话,或者刷新是什么意思?如果您确实是指通过执行干净的非人脸请求来进行实际页面刷新,那么已经有一个范围涵盖了这一点:它被称为session scope
    • 我想要一个对话范围,因为我有购物车类型的要求。我的对话 ID 名为“shoppingCart”。这一切都很好,但是,如果在第一页上用户出于某种原因在浏览器中点击刷新,它会抛出异常,因为 cid shoppingCart 已经在使用中,这是可以理解的。现在,如果我不给对话命名它会起作用,但你最终会得到一个死对话。我可以通过不命名对话来解决这个问题,我只是在追求最佳实践方法。我希望有某种方法可以重新加入购物车对话之类的。
    • 如果您希望在用户刷新浏览器时对话持续下去,那么 Java 将如何区分在不同选项卡或窗口中进行的两个对话?请记住,没有每个窗口或选项卡的 cookie 之类的东西。您的 Java 服务器看不到请求是来自第二个选项卡还是来自当前选项卡的刷新。刷新与新请求相同,此时只能发送 cookie。如果您确实找到了一种在不提供 CID 的情况下重新加入对话的方法,那么您实际上已经重新创建了会话范围。
    • 谢谢,这已经为我澄清了。我想这是使用@PostConstruct 开始对话的缺点,如果不处理刷新异常,您将无法真正使用命名对话。我想我可以捕获异常,然后将它们重定向到同一页面,但如果我真的想包含 cid=shoppingCart 参数,但定义一个 hack。
    【解决方案3】:

    JSR-299 内置对话的概念有点破旧。至少对于 JSF 应用程序而言。使用@PreRenderViewEvent 基本上会放弃这种方法的所有好处,因为它会使每个 @ConversationScoped bean longRunning。

    您可以尝试改用 Apache MyFaces CODI @ConversationScoped。 CODI 是一个 CDI 扩展库,可以很好地与 Apache OpenWebBeans 以及 Weld 一起使用。 它还提供@ViewScoped、@ViewAccessScoped(一种自动对话)和@WindowScoped 上下文。

    更多信息:https://cwiki.apache.org/confluence/display/EXTCDI/Index

    【讨论】:

    • 好吧,你肯定不想在 every @PreRenderViewEvent 上宣传 every 对话 - 同意这部分。但是通过一些简单的检查(没有回发,对话仍然是短暂的),您将基本上实现应用程序布局(不会将其称为“模式”),您将在对某个页面的每个初始请求开始长时间运行的对话。 (顺便说一句,Seam 3 view-actions 推广了相同的方法。)这是我经常使用的东西 - 我看不出它有什么根本错误 - 但我知道我正在与 CDI EG 的成员交谈: -)
    【解决方案4】:

    这一切都在文档中:

    会话范围处于活动状态:

    在任何 JSF 面孔或非面孔请求的所有标准生命周期阶段。

    对话上下文提供对与特定对话关联的状态的访问。每个 JSF 请求都有一个关联的对话。该关联由容器根据以下规则自动管理:

    任何 JSF 请求都只有一个关联的对话。 与 JSF 请求关联的对话在恢复视图阶段开始时确定,并且在请求期间不会更改。

    任何对话都处于以下两种状态之一:瞬时或长期运行。

    默认情况下,对话是短暂的 通过调用 Conversation.begin() 可以将短暂的对话标记为长时间运行 通过调用 Conversation.end() 可以将长时间运行的对话标记为瞬态

    所有长时间运行的会话都有一个字符串值的唯一标识符,它可以在会话被标记为长时间运行时由应用程序设置,或者由容器生成。

    如果与当前 JSF 请求关联的会话在 JSF 请求结束时处于瞬态,则该会话被销毁,会话上下文也被销毁。

    如果与当前 JSF 请求相关联的会话在 JSF 请求结束时处于长时间运行状态,则它不会被销毁。相反,它可能会根据以下规则传播到其他请求:

    与呈现 JSF 视图的请求相关联的长时间运行的对话上下文会自动传播到源自该呈现页面的任何面孔请求(JSF 表单提交)。 与导致 JSF 重定向(由导航规则或 JSF NavigationHandler 产生的重定向)的请求关联的长时间运行的对话上下文会自动传播到生成的非人脸请求,以及对同一 URL 的任何其他后续请求。这是通过使用名为 cid 的 GET 请求参数来完成的,该参数包含对话的唯一标识符。 与请求相关联的长时间运行的对话可以通过使用名为 cid 的 GET 请求参数传播到任何非人脸请求,该参数包含对话的唯一标识符。在这种情况下,应用程序必须管理这个请求参数。

    当没有会话传播到 JSF 请求时,该请求与新的临时会话相关联。所有长时间运行的对话都被限定在一个特定的 HTTP servlet 会话中,并且可能不会跨越会话边界。在以下情况下,传播的长时间运行的对话无法恢复并与请求重新关联:

    当 HTTP servlet 会话无效时,在 servlet service() 方法完成后,在当前会话期间创建的所有长时间运行的会话上下文都将被销毁。 允许容器任意销毁与当前没有 JSF 请求相关联的任何长时间运行的对话,以节省资源。

    作者: 加文·金,皮特·缪尔

    【讨论】:

    • 这非常有用,谢谢。就像我在其他 cmets 中提到的那样,问题是我错过了将 CID 添加到页面中的单个 AJAX 调用中,因此开始了新的对话并搞砸了。
    【解决方案5】:

    恕我直言,CDI 对话被设计破坏了,struberg 指出了一个有希望的替代方案。在我的应用程序中,我遇到了同样的问题,目前我正在将其重构为 CDI + CODI 1,感觉不错。 @ConversationScoped 解决了所有这些问题。在重构我的应用程序时,我可以使用 @ViewAccessScoped 解决很多令人讨厌的情况。感谢 struberg 为我们指明了方向!

    【讨论】:

      【解决方案6】:

      奇怪的是,如果你给你的 Facelet 添加一个事件监听器,即使它调用一个空方法,生成的源的表单动作也会有 'cid' 参数,因此,AJAX 调用不会创建一个新的对话.如果没有事件侦听器,则“cid”会丢失表单中的操作。

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

      MyBean.java

      public void dummy() {}
      

      【讨论】:

        猜你喜欢
        • 2013-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-21
        • 2012-02-23
        • 2011-06-08
        • 1970-01-01
        • 2014-02-04
        相关资源
        最近更新 更多