CDI @ConversationScoped 与 AJAX

Posted

技术标签:

【中文标题】CDI @ConversationScoped 与 AJAX【英文标题】:CDI @ConversationScoped with AJAX 【发布时间】:2011-12-24 08:36:47 【问题描述】:

我遇到了一个非常典型的 CRUD 情况,我正在努力解决,所以我想我一定是误解了一些东西。我整理了一个小演示应用程序来更好地解释我的问题。感兴趣的两个文件如下所示:

PersonView - 支持 JSF 页面的 CDI 托管 Bean

package example

import java.io.Serializable;
import java.util.List;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.inject.Named;

@ConversationScoped @Named
public class PersonView implements Serializable 

private Person selectedPerson;
@Inject private PersonService personService;
@Inject private Conversation conversation;

public PersonView() 

public List<Person> getPeople()  return personService.findAll(); 

public void beginConversation()  if( conversation.isTransient() ) conversation.begin(); 

public void endConversation()  if( !conversation.isTransient() )  conversation.end(); 

public void createPerson() 
    beginConversation();
    setSelectedPerson( new Person() );


public void addPerson() 
    personService.addPerson( getSelectedPerson() );
    endConversation();


public void updatePerson()  personService.updatePerson( getSelectedPerson() ); 

public Person getSelectedPerson()  return selectedPerson; 

public void setSelectedPerson(Person selectedPerson)  this.selectedPerson = selectedPerson; 

index.xhtml - 用于操纵人的 JSF 页面

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:p="http://primefaces.org/ui">
<h:head>
    <title>CRUD Example</title>
</h:head>
<h:body>
    <h:form prependId="false">
        <p:dataTable var="p" value="#personView.people" id="person_table" rowKey="#p.id" selection="#personView.selectedPerson">
            <p:column selectionMode="single"/>              
            <p:column><f:facet name="header">ID</f:facet>#p.id<p:column>
            <p:column><f:facet name="header">Name</f:facet>#p.name</p:column>
            <f:facet name="footer">
               <p:commandButton value="Create Person" onclick="create_dialog.show();" actionListener="#personView.createPerson"/>                 
               <p:commandButton value="Edit Person" onclick="edit_dialog.show();" update="edit_panel"/>
            </f:facet>
        </p:dataTable>
    </h:form>
    <p:dialog header="Create Person" id="create_dialog" widgetVar="create_dialog" modal="true"  >
        <h:form prependId="false">
            <p:panel id="create_panel">
                <p>Name: <p:inputText value="#personView.selectedPerson.name" required="true"/></p>
                <p><p:commandButton value="Add" actionListener="#personView.addPerson" oncomplete="create_dialog.hide();" update="person_table" /></p>
            </p:panel>
        </h:form>
    </p:dialog>
</h:body>

在索引页面上,用户会看到一个数据表,其中包含系统知道的所有人员。然后他们按下表格底部的 Create Person 按钮。我已经检查过这是否正确调用了 createPerson 方法,并且对话显然开始了。然后会显示 create_dialog,用户可以在其中输入名称。当用户单击“添加”按钮时,问题就出现了。 JSF 尝试存储人员姓名,但 selectedPerson 变量现在为 null,因此它失败并出现 NullPointerException。

我意识到这不是创建对象的常用方法,但在我当前的应用程序中是有意义的,因为我可以猜测新 Person 的一些值。它也非常适合我想要的编辑方式。

所以我的问题是:为什么对话没有传播? PersonView bean 似乎一直在请求范围内。我已经阅读了 JSF2 中的 @ViewScoped,但如果可能的话,我宁愿坚持使用 CDI。根据我的阅读,我认为问题在于未能通过请求传递 CID 名称,但我不知道如何使用 AJAX 做到这一点。

我想出的唯一解决方案是将 PersonView 移到会话中,但这感觉就像一个巨大的杂物。 e

【问题讨论】:

我不做 CDI,但这听起来很像 this issue with @ViewScoped。尝试在update 中显式引用另一个&lt;h:form&gt;。例如。 &lt;p:commandButton value="Create Person" ... update=":create_form"&gt;&lt;h:form id="create_form"&gt; 有趣,我想你已经解释了为什么当我用@ViewScoped 注释bean 时上面的代码不起作用。不过,我希望找到解决该问题的 CDI 解决方案。我发现的最好的是question,它建议使用 MyFaces CODI,这似乎可以满足我的想法。 我认为 CDI 也有同样的问题,因为它与 JSF 视图状态有关。您是否尝试过直接更新 &lt;h:form&gt; 而不是其子女或父母之一? 【参考方案1】:

将@ConversationScoped 与&lt;f:ajax&gt; 标签结合使用,需要将对话ID 传递给commandButton 和commandLink 元素。 请参阅此示例:

<h:commandLink value="update"
    actionListener="#myController.updateSomething(myData)">
    <f:ajax render="...."/>
    <f:param name="cid" value="#myController.getCID()" />
</h:commandLink>

【讨论】:

如果 id 是/曾经在原始 url 上,OmniFaces 在其o:form 中有一个功能,它会自动为所有呼叫执行此操作!!!。不管这个功能如何,使用omnifaces都是一件好事。调查它(还有 optimusfaces)【参考方案2】:

好像您正在使用 PrimeFaces,我不确定如何使用 Primefaces 进行 ajax 调用,所以我将向您展示如何使用标准 JSF 2.0 完成它

首先,在使用 AJAX 时,您必须在每个 AJAX 请求中传递cid。 尝试将以下内容添加到您的 ConversationScope 托管 Bean(您需要以某种方式将 cid 传递给视图 - index.xhtml,然后每个后续 AJAX 调用都会将相同的 cid 传递回服务器):

@Named
@ConversationScoped
public class PersonView implements Serializable 

public List<Person> getPeople()  
 return personService.findAll(); 


 @Inject 
 private Conversation conversation;

 //Start the conversation once the 
 //bean is created and all injection is done on the bean
 //I typically use this in the case of AJAX
 @PostContruct 
public void beginConversation() 
 if( conversation.isTransient() ) 
   conversation.begin();
 


public void endConversation()  
  if( !conversation.isTransient() )  
    conversation.end();
  

// This will be used in the view (index.xhtml)
public String getConversationId() 
  return conversation.getId();


public void createPerson() 
 setSelectedPerson( new Person() );


public void addPerson() 
 personService.addPerson( getSelectedPerson() );
 endConversation();
 // beginConversation(); //might need to start a new conversation once old one is done


现在在您看来,您通常会执行以下操作:

<h:commandButton action="#personView.createPerson" value="Create Person">
   <!-- passing the cid -->
   <f:param name="cid" value="#personView.conversationId" />
   <f:ajax execute="@form" />
</h:commandButton>

<h:commandButton action="#personView.addPerson" value="Add">
   <!-- passing the cid -->
   <f:param name="cid" value="#personView.conversationId" />
   <f:ajax execute="@form" render=":person_table" />
</h:commandButton>

只要所有后续调用都是 AJAX,它就可以工作。如果您开始将其与正常通话混在一起,对话就会丢失。另一方面,如果这是您正在进行的非 AJAX 调用列表,cid 将自动为您传递

【讨论】:

【参考方案3】:

我让它工作的唯一方法是在MyFaces CODI 中使用@ViewAccessScoped。 CDI 允许可扩展性,因此您所要做的就是将 CODI jar 文件包含在您的应用程序中。即使您使用的是 Mojarra 而不是 MyFaces,这也有效。

因此,如果您想使用 CDI 注释,这是我的建议。我尝试使用 ConversationScoped 批注一段时间,但我无法让它方便地工作。一旦我开始使用 CODI,我的所有问题都迎刃而解了。

【讨论】:

以上是关于CDI @ConversationScoped 与 AJAX的主要内容,如果未能解决你的问题,请参考以下文章

Weld + JSF 2.0 @ConversationScoped 不保持状态

Google Guice 与 JSR-299 CDI / Weld

如何将 CDI 限定符与多个类实现一起使用?

如何将 JPA 验证与 CDI 和 Seam 验证集成

获取 CDI 托管 bean 实例的规范方法:BeanManager#getReference() 与 Context#get()

178 - 流程引擎的查找与配置