Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does @PostConstruct callback fire every time even though bean is @ViewScoped? JSF

I am using datatable on page and using binding attribute to bind it to my backing bean. This is my code :-

<?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:p="http://primefaces.prime.com.tr/ui">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
            <h:form prependId="false">

                <h:dataTable var="item" value="#{testBean.stringCollection}" binding="#{testBean.dataTable}">
                    <h:column>
                        <h:outputText value="#{item}"/>
                    </h:column>
                    <h:column>
                        <h:commandButton value="Click" actionListener="#{testBean.action}"/>
                    </h:column>
                </h:dataTable>

            </h:form>

    </h:body>
</html>

This is my bean :-

package managedBeans;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.component.html.HtmlDataTable;

@ManagedBean(name="testBean")
@ViewScoped
public class testBean implements Serializable {

    private List<String> stringCollection;

    public List<String> getStringCollection() {
        return stringCollection;
    }

    public void setStringCollection(List<String> stringCollection) {
        this.stringCollection = stringCollection;
    }

    private HtmlDataTable dataTable;

    public HtmlDataTable getDataTable() {
        return dataTable;
    }

    public void setDataTable(HtmlDataTable dataTable) {
        this.dataTable = dataTable;
    }

    @PostConstruct
    public void init(){
        System.out.println("Post Construct fired!!");
        stringCollection = new ArrayList<String>();
        stringCollection.add("a");
        stringCollection.add("b");
        stringCollection.add("c");

    }

    public void action(){
        System.out.println("Clicked!!");

    }
}

Please tell me why is the @PostConstruct firing each and every time i click on button? It should fire only once as long as i am on same page beacause my bean is @ViewScoped. Further, if i remove the binding attribute then everything works fine and @PostConstruct callback fires only once. Then why every time when i use binding attribute? I need binding attribute and want to perform initialisation tasks like fetching data from webservice, etc only once. What should i do? Where should i write my initialisation task?

like image 626
TCM Avatar asked May 09 '10 09:05

TCM


2 Answers

Interesting, when you're using component binding on a view scoped bean, the view scope breaks.

I am not sure if that is a bug in JSF2, I would have to read the entire JSF2 specification first. As far now your best bet is to drop the component binding for now and pass the selected item via new EL 2.2 method argument syntax:

<h:dataTable var="item" value="#{testBean.stringCollection}">
    <h:column>
        <h:outputText value="#{item}"/>
    </h:column>
    <h:column>
        <h:commandButton value="Click" action="#{testBean.action(item)}"/>
    </h:column>
</h:dataTable>

See also:

  • How can I pass selected row to commandLink inside dataTable?
  • Invoke direct methods or methods with arguments / variables / parameters in EL
  • Benefits and pitfalls of @ViewScoped

Update (Dec 2012): this is indeed a bug in JSF2. It's a chicken-egg issue. The view scoped beans are stored in the JSF view state. So the view scoped beans are only available after restore view phase. However, the binding attribute runs during restore view phase, while the view scoped beans are not available yet. This causes creation of a brand new view scoped bean instance, which is then later replaced by the real view scoped bean which was stored in the restored JSF view state.

This is reported as JSF issue 1492 and JSF spec isssue 787 which will be fixed for JSF 2.2. Until then, your best bet is to use binding on request scoped beans exclusively, or to look for alternate ways for the particular functional requirement.


Update (Mar 2015): The JSF 2.2 fix was backported to Mojarra 2.1.18. So if you're still using JSF 2.0/2.1, you'd best upgrade to at least that version. See also a.o. What is component binding in JSF? When it is preferred to be used? and JSTL in JSF2 Facelets... makes sense?

like image 158
BalusC Avatar answered Nov 16 '22 23:11

BalusC


As other said, I would say that the best thing to do is to drop component binding (you don't need it here).

But I would add that you can achieve the same as you're trying to do in a more object-oriented fashion by using action parameters, like this:

<h:commandButton value="Click" action="#{testBean.action(item)}"/>

... and in your java code:

  public void action(Item item){
    System.out.println("Clicked!!" + item);
}
like image 24
ymajoros Avatar answered Nov 16 '22 23:11

ymajoros