问题
I'm developing a view which consists of an object creation form and the existing object list, creablocco.xhtml:
<?xml version="1.0"?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:ace="http://www.icefaces.org/icefaces/components"
xmlns:ice="http://www.icesoft.com/icefaces/component"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
template="/resources/includes/templates/template.xhtml">
<ui:param name="pageTitle" value="Crea blocco" />
<ui:define name="page-content">
<ace:panel id="personalInfoInputPanel" header="Crea blocco">
<div class="div_creablocco">
<h:form id="creabloccoform">
<h:panelGrid id="creabloccoInputGrid" columns="1" styleClass="table_creablocco">
<ace:textEntry id="numeroFile" value="#{creaBloccoBean.numeroFile}" label="Numero file:" labelPosition="left" required="true" requiredIndicator="(*)" indicatorPosition="right">
</ace:textEntry>
<ace:textEntry id="numeroColonne" value="#{creaBloccoBean.numeroColonne}" label="Numero colonne:" labelPosition="left" required="true" requiredIndicator="(*)" indicatorPosition="right">
</ace:textEntry>
<ace:textEntry id="posizioneX" value="#{creaBloccoBean.posizioneX}" label="Coordinata X:" labelPosition="left" required="true" requiredIndicator="(*)" indicatorPosition="right">
</ace:textEntry>
<ace:textEntry id="posizioneY" value="#{creaBloccoBean.posizioneY}" label="Coordinata Y:" labelPosition="left" required="true" requiredIndicator="(*)" indicatorPosition="right">
</ace:textEntry>
<ace:pushButton id="pushBttnCreaBlocco" value="Crea blocco" action="creaBlocco"/>
</h:panelGrid>
</h:form>
</div>
</ace:panel>
<ace:panel id="blocchiPanel" header="Blocchi">
<h:form id="blocchiTableForm">
<ace:menuBar id="iconeBloccoMenu" rendered="#{!blocchiBean.modificaInCorso}">
<ace:menuItem id="dettagliBloccoButton" value="Dettagli" action="dettagliBlocco" icon="ui-icon ui-icon-search" />
<ace:menuItem id="modificaBloccoButton" value="Modifica" action="modificaBlocco" icon="ui-icon ui-icon-pencil" />
<ace:menuItem id="cancellaBloccoButton" value="Cancella" action="cancellaBlocco" icon="ui-icon ui-icon-closethick" />
</ace:menuBar>
<ace:menuBar id="iconeModificaBloccoMenu" rendered="#{blocchiBean.modificaInCorso}">
<ace:menuItem id="annullaModificheButton" value="Annulla" action="annullaModificheBlocco" icon="ui-icon ui-icon-arrow-1-w" />
<ace:menuItem id="salvaModifiche" value="Salva" action="salvaModificheBlocco" icon="ui-icon ui-icon-disk" />
</ace:menuBar>
<ace:dataTable id="blocchiTable" value="#{blocchi}"
binding="#{blocchiBean.table}" stateMap="#{blocchiBean.stateMap}"
selectionMode="multiple" doubleClickSelect="false"
rowSelectListener="#{blocchiBean.selectListener}"
rowUnselectListener="#{blocchiBean.deselectListener}" var="blocco"
lazy="true" rows="10" paginator="true" paginatorPosition="bottom">
<ace:column id="idBloccoColumn" headerText="ID" styleClass="id">
<h:outputText id="idBloccoText" value="#{blocco.id}" />
</ace:column>
<ace:column id="numeroFileBloccoColumn" headerText="Numero file"
styleClass="numFile">
<ace:cellEditor>
<f:facet name="output">
<h:outputText id="numeroFileBloccoText"
value="#{blocco.numeroFile}" />
</f:facet>
<f:facet name="input">
<h:inputText id="numeroFileBloccoInput"
value="#{blocco.numeroFile}" />
</f:facet>
</ace:cellEditor>
</ace:column>
<ace:column id="numeroColonneBloccoColumn"
headerText="Numero colonne" styleClass="numColonne">
<ace:cellEditor>
<f:facet name="output">
<h:outputText id="numeroColonneBloccoText"
value="#{blocco.numeroColonne}" />
</f:facet>
<f:facet name="input">
<h:inputText id="numeroColonneBloccoInput"
value="#{blocco.numeroColonne}" />
</f:facet>
</ace:cellEditor>
</ace:column>
<ace:column id="posizioneXBloccoColumn" headerText="Posizione X"
styleClass="posX">
<ace:cellEditor>
<f:facet name="output">
<h:outputText id="posizioneXBloccoText"
value="#{blocco.posizioneX}" />
</f:facet>
<f:facet name="input">
<h:inputText id="posizioneXBloccoInput"
value="#{blocco.posizioneX}" />
</f:facet>
</ace:cellEditor>
</ace:column>
<ace:column id="posizioneYBloccoColumn" headerText="Posizione Y"
styleClass="posY">
<ace:cellEditor>
<f:facet name="output">
<h:outputText id="posizioneYBloccoText"
value="#{blocco.posizioneY}" />
</f:facet>
<f:facet name="input">
<h:inputText id="posizioneYBloccoInput"
value="#{blocco.posizioneY}" />
</f:facet>
</ace:cellEditor>
</ace:column>
</ace:dataTable>
</h:form>
</ace:panel>
</ui:define>
</ui:composition>
The "object creation form" (<h:form id="creabloccoform">
) is made up of text inputs and a button (<ace:pushButton id="pushBttnCreaBlocco" ... />
).
The "existing object list" is a table (<ace:dataTable id="blocchiTable" .. />
) and serves as a starting point to edit the selected object attributes or to delete the selected objects.
The removal and the editing process is accomplished by selecting some rows in the table and pressing the proper button in a menu bar (look at <ace:menuBar id="iconeBloccoMenu" .. />
and <ace:menuBar id="iconeModificaBloccoMenu" ... />
).
This is the flow defined using Spring Webflow, creablocco-flow.xml:
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<var name="creaBloccoBean" class="com.infoone.siglo.viewbeans.CreaBloccoBean" />
<view-state id="creablocco">
<on-entry>
<evaluate expression="blocchiBean.getBlocchi()" result="flowScope.blocchi" />
</on-entry>
<transition on="creaBlocco" to="fine">
<evaluate expression="gestoreBlocchiCtr.creaBlocco(creaBloccoBean)" />
</transition>
<transition on="cancellaBlocco" to="decidiCancellazione">
<evaluate expression="blocchiBean.getRigheSelezionate()" result="flowScope.righeSelezionate"/>
</transition>
<transition on="modificaBlocco" to="decidiModifica">
<evaluate expression="blocchiBean.getRigheSelezionate()" result="flowScope.righeSelezionate"/>
</transition>
</view-state>
<decision-state id="decidiCancellazione">
<if test="!(flowScope.righeSelezionate).isEmpty()" then="cancellaBlocco" else="fine" />
</decision-state>
<decision-state id="decidiModifica">
<if test="!(flowScope.righeSelezionate).isEmpty()" then="modificaBlocco" else="fine" />
</decision-state>
<action-state id="cancellaBlocco">
<evaluate expression="gestoreBlocchiCtr.rimuoviBlocchi(flowScope.righeSelezionate)" />
<transition to="fine"/>
</action-state>
<action-state id="modificaBlocco">
<evaluate expression="blocchiBean.abilitaModifica()"/>
<transition to="fine"/>
</action-state>
<end-state id="fine" />
</flow>
This is the bean which is causing the problem mentioned in the title, BlocchiBean.java:
package com.infoone.siglo.viewbeans;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import javax.annotation.PostConstruct;
import javax.faces.bean.ViewScoped;
import org.icefaces.ace.component.column.Column;
import org.icefaces.ace.component.datatable.DataTable;
import org.icefaces.ace.event.SelectEvent;
import org.icefaces.ace.event.UnselectEvent;
import org.icefaces.ace.model.table.RowState;
import org.icefaces.ace.model.table.RowStateMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.infoone.siglo.viewbeans.lazyLoaders.BlocchiLazyLoader;
@Component
@ViewScoped
public class BlocchiBean implements Serializable {
/**
*
*/
private static final long serialVersionUID = -7007046069743684498L;
@Autowired
private transient BlocchiLazyLoader blocchi;
private DataTable table;
private RowStateMap stateMap;
private Set<CreaBloccoBean> righeSelezionate;
private boolean modificaInCorso;
public BlocchiBean()
{
}
@SuppressWarnings("unused")
@PostConstruct
private void initBlocchi()
{
stateMap = new RowStateMap();
stateMap.setAllSelectable(true);
righeSelezionate = new ConcurrentSkipListSet<CreaBloccoBean>();
blocchi.setRowCount(blocchi.getPageSize());
modificaInCorso = false;
}
//BlocchiLazyLoader blocchi
public void setBlocchi(BlocchiLazyLoader blocchi) {
this.blocchi = blocchi;
}
public BlocchiLazyLoader getBlocchi() {
return blocchi;
}
//RowStateMap stateMap
public RowStateMap getStateMap() {
return stateMap;
}
public void setStateMap(RowStateMap stateMap) {
this.stateMap = stateMap;
}
//DataTable table
public DataTable getTable() {
return table;
}
public void setTable(DataTable table) {
this.table = table;
}
//Selection listeners
public void selectListener(SelectEvent event) {
Object[] oggettiSelezionati = event.getObjects();
for (Object oggettoSelezionato : oggettiSelezionati) {
CreaBloccoBean bloccoSelezionato = (CreaBloccoBean) oggettoSelezionato;
righeSelezionate.add(bloccoSelezionato);
}
}
public void deselectListener(UnselectEvent event) {
Object oggettoDeselezionato = event.getObject();
CreaBloccoBean bloccoDeselezionato = (CreaBloccoBean) oggettoDeselezionato;
righeSelezionate.remove(bloccoDeselezionato);
}
//List<CreaBloccoBean> righeSelezionate
public List<CreaBloccoBean> getRigheSelezionate() {
CreaBloccoBean[] array = new CreaBloccoBean[righeSelezionate.size()];
array = righeSelezionate.toArray(array);
return Arrays.asList(array);
}
public void setRigheSelezionate(List<CreaBloccoBean> righeSelezionate) {
this.righeSelezionate.clear();
this.righeSelezionate.addAll(righeSelezionate);
}
public boolean isModificaInCorso() {
return modificaInCorso;
}
public void setModificaInCorso(boolean modificaInCorso) {
this.modificaInCorso = modificaInCorso;
}
//Abilitazione dell'editing
public void abilitaModifica() {
if (!righeSelezionate.isEmpty())
{
modificaInCorso = true;
List<Column> colonne = table.getColumns();
for (Object oggettoCorrente : stateMap.getSelected()) {
CreaBloccoBean beanCorrente = (CreaBloccoBean) oggettoCorrente;
RowState statoCorrente = stateMap.get(beanCorrente);
if (righeSelezionate.contains(beanCorrente)) {
for (Column colonna : colonne) {
statoCorrente.addActiveCellEditor(colonna.getCellEditor());
}
}
}
}
}
}
Now, let's assume I'm accessing the flow using the following URL: http://<project base URL>/<flow path>?execution=<execution key 1>
and I select one of the rows. Obviously, the stateMap and righeSelezionate attributes of the BlocchiBean object will be properly update by the underlying engine (the latter one through the (de)selection listeners). After that, I click on the Modifica button (the Italian for Edit) and the *cellEditor*s get properly activated. It's all ok, by far.
Here comes the problem. If I change the execution key in the address bar, obtaining something like http://<project base URL>/<flow path>?execution=<execution key 2>
, and I press Return (therefore starting another instance of the flow), the starting view of the flow gets opened again, but the "state of the table" doesn't look reset. The row I selected is still selected and the stateMap and righeSelezionate attributes of the BlocchiBean object look unchanged. This is pretty strange, especially because BlocchiBean is declared as @ViewScoped:
@Component
@ViewScoped
public class BlocchiBean implements Serializable {
therefore it's expected to be recreated from scratch when a new instance of a flow starts.
I already tried to play with the scopes, but it didn't solve the problem.
Just for completeness, I mention the previous annotations of the BlocchiBean object:
@Component("blocchiBean")
@ViewScoped
public class BlocchiBean implements Serializable {
My aim was to match the component name with the bean name used in the view and in the flow. Unfortunately, this annotation doesn't solve the problem, either.
回答1:
I've found a remedy.
I'm not able yet to fully explain why the solution provided in my question isn't working but, at least, I came up with a correct one.
I declared a flow variable in my flow:
<var name="blocchiBean" class="com.infoone.siglo.viewbeans.BlocchiBean" />
and removed any annotation from the class definition:
package com.infoone.siglo.viewbeans;
//imports
public class BlocchiBean implements Serializable {
For those who did it, don't explicitly declare it as a bean in your xml configuration files. It has to be only a Spring Flow variable and nothing else.
It works, now. Anyway, don't expect Spring Web Flow to use always the same BlocchiBean object within the same flow instance, even if it is inherently flowScoped (it's a flow variable, therefore it's flowScoped).
Indeed, if you run a debugger and look at object ids, you'll find that Spring Web Flow uses different instances throughout the same flow instance. It sounds strange at first but, in the end, it works: indeed, everytime a bean instance is garbage collected, its state is properly saved "somewhere", so that the state of next instance within the same flow execution can be set through its setter methods, just after its instantiation.
Obviously, if another instance of the flow starts, a fresh new flow variable will be constructed and its state won't be altered by invoking its setter methods. In other words, it will behave as a flowScoped variable is expected to behave.
来源:https://stackoverflow.com/questions/12205348/unexpected-survival-of-a-viewscoped-bean