When a form is handled in JSF, does it all happen

2019-08-24 18:07发布

问题:

Say I have this piece of code

        <p:dataTable styleClass="scheduleTable" value="#{todaySchedule.hours}" var="hour">
            <p:column headerText="Hour" styleClass="hourColumn" >
                #{hour.time}
            </p:column>
        </p:dataTable>

and in a class called todaySchedule, have a method

public List<Hour> getHours() {
        final List<Hour> hours = IntStream.range(0, Hour.TIME.values().length)
                                          .mapToObj($ -> new Hour()).collect(Collectors.toList());
        for (int i = 0; i < 5; i++) {
             hours.get(i).setHour(1);
        }
        return hours;
}

and here's the Hour class

public class Hour {
    private int time;

    public int getTime() {
        return time;
    }

    public void setTime(int time) {
        this.time = time;
    }
}

Now, I'm not sure what JSF does behind the scenes to make this dynamic dataTable data iteration through the hours List possible, but I assume that if this is happening all in one thread, then it is okay. However, what if behind the scenes, the getHours is used in another thread that actually does the generating columns and see Hour in a bad state? This could be avoided if the getHours() method was

public List<Hour> getHours() {
        final List<Hour> hours = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            hours.add(new Hour(i + ""));
        }
        return hours;
}

with the corresponding Hour class being

public class Hour {
    private final int time;

    public Hour(int time) {
         this.time = time;
    }

    public int getTime() {
         return time;
    }
}

However, my question is that, if it wasn't changed to the latter design, can things go wrong when using basic dynamic JSF dataTable rendering due to visibility issues in Java when publishing this Hour instances?

回答1:

JSF runs on top of Servlet API which serves one request in one thread, so unless you introduce more threads yourself you may expect your code to be run in a single thread.

On the other hand, you should get to know the basics of JSF life-cycle and how the bean properties are being accessed as this may cause you a lot of troubles if you don't understand it.

For example, should your Java code stay the same, adding another reference to todaySchedule.hours to your JSF would cause the getter to be called twice, thus generating the content twice. This can get really messy very quickly, so it is a good thing to do some "caching". I personally use this approach:

private List<Hour> hours = null;

private void initHours() {
    this.hours = new LinkedList<>();
    // Fill hours here
}

public List<Hour> getHours() {
    if (this.hours == null) {
        initHours();
    }
    return this.hours;
}

You have to be careful though at what stage of the JSF lifecycle you do this. Should you for example in a form processing method change the data affecting the list generation, the list will probably be already "cached" from the restore-view phase and the change would not be reflected when render-view phase starts. In such case you should be aware of the caching and clear the list when you need to reload it.

public void saveHours() {
    // Save the data, do whatever you need to do

    // This will ensure re-initializing the list on the next call
    this.hours = null;
}