【问题标题】:JSF CDI Conversation scopeJSF CDI 对话范围
【发布时间】:2016-07-12 13:20:52
【问题描述】:

我对 CDI 范围有疑问。

我有一个会话范围为 ClientController 的 bean,其中我有要按服务顺序推送的客户端和电话对象。当我注册一个新客户时,我可以向这个客户推送一部或多部手机。为此,我使用了对话范围。然而,向我的客户端推送新手机的每个请求都在执行 @PostContruct 方法,使 bean 失去其状态,即使我在推送第一部手机时也开始对话。

起初,我猜是 bean 配置的问题,但是当我删除在客户端页面上声明的模板时,应用程序可以正常工作。此模板使用具有会话范围的 bean,由用户选择控制页面语言。

接下来是我的代码,你可以通过这个链接https://github.com/mcqueide/service-order在 github 存储库上关注我的代码。

ClientController.java

package br.com.codeshare.controller;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.inject.Produces;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;

import br.com.codeshare.enums.ErrorCode;
import br.com.codeshare.exception.BusinessException;
import br.com.codeshare.model.Client;
import br.com.codeshare.model.Phone;
import br.com.codeshare.qualifiers.SessionMap;
import br.com.codeshare.service.ClientService;
import br.com.codeshare.service.PhoneService;
import br.com.codeshare.util.WebResources;

@Named
@ConversationScoped
public class ClientController implements Serializable {

    private static final long serialVersionUID = 1L;

    @Inject
    private FacesContext facesContext;
    @Inject @SessionMap
    private Map<String, Object> sessionMap;
    @Inject
    private ClientService clientService;

    private Client newClient;

    @Inject
    private PhoneController phoneController;
    @Inject
    private PhoneService phoneService;

    @Inject
    private Conversation conversation;

    private String filterName;

    private List<Client> listClients;

    private Client clientSelected;
    private List<Phone> phoneToBeRemove;

    @Produces
    @Named
    public Client getNewClient() {
        return newClient;
    }

    @PostConstruct
    public void initNewClient() {
        newClient = new Client();
        newClient.setTelefones(new ArrayList<Phone>());
        listClients = clientService.findAll();
    }

    public String save() throws Exception {
        try {
            validatePhoneLeastOnePhoneObligatory(newClient);

            clientService.save(newClient);
            facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, WebResources.getMessage("register"),WebResources.getMessage("sucess_register")));
            initNewClient();
        }catch (BusinessException e) {
            FacesMessage m = new FacesMessage(FacesMessage.SEVERITY_ERROR,WebResources.getMessage(e.getErrorCode()),"");
            facesContext.addMessage(null, m);
        }catch (Exception e) {
            String errorMessage = getRootErrorMessage(e);
            FacesMessage m = new FacesMessage(FacesMessage.SEVERITY_ERROR,errorMessage,WebResources.getMessage("unsuccessful"));
            facesContext.addMessage(null, m);
        }
        if(!conversation.isTransient()){
            conversation.end();
        }
        return null;
    }

    public String update(Client client) throws Exception{
        try {
            validatePhoneLeastOnePhoneObligatory(client);

            clientService.update(client,phoneToBeRemove);
            facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,  WebResources.getMessage("register"),WebResources.getMessage("sucess_register")));
            initNewClient();
        }catch (BusinessException e) {
            FacesMessage m = new FacesMessage(FacesMessage.SEVERITY_ERROR,WebResources.getMessage(e.getErrorCode()),"");
            facesContext.addMessage(null, m);
            return null;
        } catch (Exception e) {
            String errorMessage = getRootErrorMessage(e);
            FacesMessage m = new FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage, WebResources.getMessage("unsuccessful"));
            facesContext.addMessage(null, m);
            return null;
        }
        if(!conversation.isTransient()){
            conversation.end();
        }
        return "clients";
    }

    private void validatePhoneLeastOnePhoneObligatory(Client client) throws BusinessException {
        if(client.getHomePhone().isEmpty() && client.getBisenessPhone().isEmpty()){
            throw new BusinessException(ErrorCode.LEAST_ONE_PHONE_OBLIGATORY.getErrorCode());
        }
    }

    private String getRootErrorMessage(Exception e) {
        String errorMessage = "Registration failed. See server log for more information";
        if (e == null) {
            return errorMessage;
        }

        Throwable t = e;
        while (t != null) {
            errorMessage = t.getLocalizedMessage();
            t = t.getCause();
        }
        return errorMessage;
    }

    public void addClientPhone() {
        if(conversation.isTransient()){
            conversation.begin();
        }

        phoneController.getNewPhone().setClient(newClient);
        if (newClient.getPhones() == null) {
            newClient.setTelefones(new ArrayList<Phone>());
        }
        newClient.getPhones().add(phoneController.getNewPhone());
        phoneController.initNewPhone();
    }

    public void removeClientPhone(Phone phone){
        if(conversation.isTransient()){
            conversation.begin();
        }

        clientSelected.getPhones().remove(phone);
        if(phoneToBeRemove == null){
            phoneToBeRemove = new ArrayList<Phone>();
        }
        phoneToBeRemove.add(phone);
    }

    public void addClientPhoneOnUpdate() {
        if(conversation.isTransient()){
            conversation.begin();
        }

        phoneController.getNewPhone().setClient(clientSelected);
        if (clientSelected.getPhones() == null) {
            clientSelected.setTelefones(new ArrayList<Phone>());
        }
        clientSelected.getPhones().add(phoneController.getNewPhone());
        phoneController.initNewPhone();
    }

    public void searchByName() {
        listClients = null;
        if(filterName == null){
            listClients = clientService.findAll();
        }
        listClients = clientService.findByName(filterName);
    }

    public String edit(Client client) {
        if(conversation.isTransient()){
            conversation.begin();
        }
        this.clientSelected = client;
        List<Phone> phoneList = phoneService.findPhoneByClientId(clientSelected.getId());
        clientSelected.setTelefones(phoneList);
        sessionMap.put("client", client);
        return "update_client";
    }

    public Client getClientSelected() {
        return (Client) sessionMap.get("client");
    }

    public String getFilterName() {
        return filterName;
    }

    public void setFilterName(String filterName) {
        this.filterName = filterName;
    }

    public List<Client> getListClients() {
        return listClients;
    }

}

Language.java

package br.com.codeshare.util;

import java.io.Serializable;
import java.util.Locale;

import javax.annotation.PostConstruct;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@SessionScoped
public class Language implements Serializable {

    private static final long serialVersionUID = 1L;

    @Inject
    private FacesContext facesContext;

    @PostConstruct
    public void init(){
        localeCode = "pt";
        countryLocaleCodeChanged();
    }

    private String localeCode;

    public String getLocaleCode() {
        return localeCode;
    }

    public void setLocaleCode(String localeCode) {
        this.localeCode = localeCode;
    }

    // value change event listener
    public void countryLocaleCodeChanged() {
        facesContext.getViewRoot().setLocale(new Locale(localeCode));
    }
}

client.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:p="http://primefaces.org/ui"
    xmlns:ui="http://java.sun.com/jsf/facelets" template="/template.xhtml">

    <ui:define name="titulo">
        #{label['client.title']}
    </ui:define>

    <ui:define name="body">
        <h:form id="form">
            <p:messages />
            <p:fieldset legend="#{label['client.fieldset.client']}" id="client">
                <p:panelGrid columns="1" styleClass="panelGrid-semBorda">
                    <p:outputLabel for="name" value="#{label['client.name']}" />
                    <p:inputText id="name" value="#{newClient.name}" />

                    <p:outputLabel for="adress" value="#{label['client.adress']}" />
                    <p:inputText id="adress" value="#{newClient.adress}" />

                    <p:fragment rendered='#{!language.localeCode.equals("en")}'>
                        <p:panelGrid columns="1" styleClass="panelGrid-semBorda">
                            <p:outputLabel for="homePhone_pt" value="#{label['client.homePhone']}" />
                            <p:inputMask id="homePhone_pt" value="#{newClient.homePhone}" mask="(99)99999-9999"/>

                            <p:outputLabel for="bisenessPhone_pt" value="#{label['client.businessPhone']}" />
                            <p:inputMask id="bisenessPhone_pt" value="#{newClient.bisenessPhone}" mask="(99)9999-9999"/>
                        </p:panelGrid>
                    </p:fragment>

                    <p:fragment rendered='#{language.localeCode.equals("en")}'>
                        <p:panelGrid columns="1" styleClass="panelGrid-semBorda">
                            <p:outputLabel for="homePhone_en" value="#{label['client.homePhone']}" />
                            <p:inputText id="homePhone_en" value="#{newClient.homePhone}"/>

                            <p:outputLabel for="bisenessPhone_en" value="#{label['client.businessPhone']}" />
                            <p:inputText id="bisenessPhone_en" value="#{newClient.bisenessPhone}"/>
                        </p:panelGrid>
                    </p:fragment>
                </p:panelGrid>
            </p:fieldset>
            <p:fieldset legend="#{label['client.fieldset.phone']}" id="phones">
                <p:panelGrid id="phone" columns="1" styleClass="panelGrid-semBorda">
                    <p:outputLabel for="brand" value="#{label['phone.brand']}" />
                    <p:inputText id="brand" value="#{newPhone.brand}" />

                    <p:outputLabel for="model" value="#{label['phone.model']}" />
                    <p:inputText id="model" value="#{newPhone.model}" />

                    <p:outputLabel for="state" value="#{label['phone.state']}"/>
                    <p:selectOneRadio id="state" value="#{newPhone.state}">
                        <f:selectItems value="#{phoneStates}" var="p" itemValue="#{p}" itemLabel="#{label[p.label]}" />
                    </p:selectOneRadio>

                    <p:outputLabel for="esn" value="#{label['phone.esn']}" />
                    <p:inputText id="esn" value="#{newPhone.esn}" />
                </p:panelGrid>
                <p:commandButton value="#{label['phone.add']}" action="#{clientController.addClientPhone}" update="phoneTable phones"/>

                <p:dataTable value="#{newClient.phones}" var="phone" emptyMessage="#{label['phone.notadd']}"
                    id="phoneTable">
                    <p:column headerText="#{label['phone.brand']}">
                        <p:outputLabel value="#{phone.brand}"/>
                    </p:column>

                    <p:column headerText="#{label['phone.model']}">
                        <p:outputLabel value="#{phone.model}"/>
                    </p:column>
                </p:dataTable>
            </p:fieldset>
            <p:commandButton action="#{clientController.save}" value="#{label['client.save']}" update="@form"/>


        </h:form>
    </ui:define>
</ui:composition>

template.xhtml

<?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:f="http://java.sun.com/jsf/core"
     xmlns:h="http://java.sun.com/jsf/html"
     xmlns:p="http://primefaces.org/ui"
     xmlns:ui="http://java.sun.com/jsf/facelets">
<f:view locale="#{language.localeCode}" encoding="utf-8">
<h:head>
    <title>
        <ui:insert name="title"/>
    </title>
    <link rel="stylesheet" type="text/css" href="resources/css/reset.css" />
    <link rel="stylesheet" type="text/css" href="resources/css/style.css" />
    <link rel="stylesheet" type="text/css" href="resources/css/fonts/font-awesome.min.css" />
</h:head>
<body>
    <div class="main">
        <div class="menu">
            <ui:include src="/menu.xhtml" />
        </div>  

        <div id="body">
            <ui:insert name="body"/>
        </div>
    </div>
</body>
</f:view>
</html>

menu.xhtml

<?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:f="http://java.sun.com/jsf/core"
     xmlns:h="http://java.sun.com/jsf/html"
     xmlns:p="http://primefaces.org/ui"
     xmlns:ui="http://java.sun.com/jsf/facelets">

    <ui:composition>
        <p:menubar>
            <p:submenu label="#{label['menu.serviceorder']}">
                <p:menuitem value="#{label['menu.serviceorder']}" url="/service-order.jsf"/>
                <p:menuitem value="#{label['menu.serviceorder.new']}" url="/new-service-order.jsf"/>
            </p:submenu>
            <p:submenu label="#{label['menu.client']}">
                <p:menuitem value="#{label['menu.client.new']}" url="/client.jsf"></p:menuitem>
                <p:menuitem value="#{label['menu.clients']}" url="/clients.jsf"></p:menuitem>
            </p:submenu>
        </p:menubar>
        <h:form class="menu_languages">
            <p:selectOneMenu value="#{language.localeCode}">
                <f:selectItem itemLabel="Português" itemValue="pt" />
                <f:selectItem itemLabel="English" itemValue="en" />
                <p:ajax listener="#{language.countryLocaleCodeChanged}" update="@all" />                        
            </p:selectOneMenu>
        </h:form>
    </ui:composition>

</html>

【问题讨论】:

    标签: jsf cdi


    【解决方案1】:

    我非常确信您遇到了以下问题之一:

    • 对话结束
      • 尝试使用一些 @PreDestroy 方法来查看对话何时消失
      • 确保您之前没有end() 对话(检查您的代码意味着调用save/update)
    • 每次添加电话时都会创建新对话(这很可能是原因)
      • 当您希望另一个请求与您正在运行的对话相关联时,您需要使用对话 ID(传播它)
      • 注意,可以通过调用conversation.getId()获取ID
      • 要验证这一点,请检查您的 URL 是否包含给定的对话 ID
      • 还请注意,如果您每次都创建一个新的对话,旧的长期运行的对话仍然挂在那里

    通过将cid(对话 ID)附加到请求 URL 来完成对话的传播。这是CDI spec(我建议您阅读)的引述,解释了对话何时自动传播:

    如果当前 Servlet 请求是 JSF 请求,并且会话处于长时间运行状态,则按照以下规则进行传播:

    • 与呈现 JSF 视图的请求关联的长时间运行的对话上下文会自动传播到源自该呈现页面的任何面孔请求(JSF 表单提交)。

    • 与导致 JSF 重定向(由导航规则或 JSF NavigationHandler 产生的重定向)的请求关联的长时间运行的对话上下文会自动传播到生成的非人脸请求以及任何其他后续请求相同的 URL。这是通过使用名为 cid 的请求参数来实现的,该参数包含对话的唯一标识符。

    【讨论】:

    • 我已经意识到问题不在于我在第一条消息中指出的模板,我已经做了很多测试,当我从模板中删除标签 时,我的范围工作正常,并且不启动@PostContruct,我可以在我的 URL 上看到 cid 参数。但是我需要 来声明我的 css,那么我该如何纠正这种情况呢? JSF 有一个标签来纠正这个说法吗?或者如何将 CID 放在 URL 参数上?
    • 当您开始对话时,即添加手机时,您可以尝试强制重新渲染整个form (&lt;h:form id="form"&gt;),即应该与 CID 一起工作。或者即将使用该 CID 的所有组件。这样,从该表单调用的任何进一步操作都应该有一个链接到它们的 CID(自动传播)。除非真的有必要,否则我会尽量避免手动将 CID 放在任何地方。
    【解决方案2】:

    我可以用这个解决这个问题:

    <f:metadata>
        <f:event listener="#{clientController.initConversation()}" type="preRenderView" />
    </f:metadata>
    

    现在,当我的页面被渲染时,我在我的 post action 上有了我的 cid。但我不知道这是否是解决这个问题的更好方法,因为我想在用户点击添加电话时将我的事务转换为长时间运行,所以如果有人有更好的想法,请分享。

    【讨论】:

      猜你喜欢
      • 2011-05-13
      • 2014-07-17
      • 1970-01-01
      • 2017-06-13
      • 2012-05-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-03
      相关资源
      最近更新 更多