Senin, 03 Februari 2014

The benefits and pitfalls of @ViewScoped






Introduction



To prepare for a new set of JSF 2.0 targeted articles (have patience, I'd like to wait for Eclipse Helios and Tomcat 7 to be finished), I've played intensively with JSF 2.0 and Facelets the last weeks (to be precise, with Mojarra 2.0.2). The new JSF 2.0 annotations and implicit navigation (the outcome will implicitly go to /outcomevalue.xhtml page) are great and very useful. No hassling with faces-config.xml anymore. It's awesome.



JSF 2.0 also introduces an important new scope and offers the possibility to define your own custom scopes. The new scope is the view scope. It lies between the existing and well-known request and session scopes in. You can put the managed bean in the view scope using the @ViewScoped annotation.




package com.example;

import java.io.Serializable;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

// ...

}


Note that the bean needs to implement Serializable as it will be stored in the view map which is in turn stored in the session. Some servletcontainer configurations will namely store sessions on disk instead of in memory. This is also mandatory when you have configured JSF view state saving method to client instead of (default) server.





You'll probably recognize yourself in abusing the session scope for data which you would like to retain in the subsequent requests to avoid the expensive task of reloading it from the database on every request again and again, such as the datamodel for the in order to be able to retrieve the selected row. A major caveat is that the changes are reflected in every opened window/tab in the same session which leads to unintuitive webapplication behaviour and thus bad user experience. Tomahawk was early to introduce a solution for this in flavor of and . The first will store the given model value temporarily in the viewroot and set it back in the model during the restore view phase of the subsequent request. The second does that for the datatable's value. Hereafter JBoss Seam came with the Conversation Scope and Apache MyFaces Orchestra followed shortly. Both saves the bean state in the session among requests, identified by an extra request parameter.



You'll probably also recognize the issue of the or action not being fired because the rendered attribute of the component or one of its parents returns false during the form submit, while it was true during the initial request. You would need to fall back to the session scope or grab Tomahawk's to fix it. How annoying!



The new view scope should solve exactly those issues. A @ViewScoped bean will live as long as you're submitting the form to the same view again and again. In other words, as long as when the action method(s) returns null or even void, the bean will be there in the next request. Once you navigate to a different view, then the bean will be trashed.



Back to top


Really simple CRUD



Here's a quick'n'dirty example in flavor of a really simple CRUD on a single page how you could take benefit of it in combination with a datatable.




package com.example;

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.model.DataModel;
import javax.faces.model.ListDataModel;

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

private List list;
private transient DataModel model;
private Item item = new Item();
private boolean edit;

@PostConstruct
public void init() {
// list = dao.list();
// Actually, you should retrieve the list from DAO. This is just for demo.
list = new ArrayList();
list.add(new Item(1L, "item1"));
list.add(new Item(2L, "item2"));
list.add(new Item(3L, "item3"));
}

public void add() {
// dao.create(item);
// Actually, the DAO should already have set the ID from DB. This is just for demo.
item.setId(list.isEmpty() ? 1 : list.get(list.size() - 1).getId() + 1);
list.add(item);
item = new Item(); // Reset placeholder.
}

public void edit() {
item = model.getRowData();
edit = true;
}

public void save() {
// dao.update(item);
item = new Item(); // Reset placeholder.
edit = false;
}

public void delete() {
// dao.delete(item);
list.remove(model.getRowData());
}

public List getList() {
return list;
}

public DataModel getModel() {
if (model == null) {
model = new ListDataModel(list);
}

return model;
}

public Item getItem() {
return item;
}

public boolean isEdit() {
return edit;
}

// Other getters/setters are actually unnecessary. Feel free to add them though.

}


Note: the outcommented dao.somemethod() lines are what you actually should do as well in real code. Also note that the DataModel is lazily instantiated in the getter, because it doesn't implement Serializable and it would otherwise be null after deserialization.



The Item class is just a simple model object, its code should be straightforward enough. A Serializable Javabean with two properties Long id and String value, a default constructor and a constructor filling both properties, a bunch of appropriate getters/setters, equals() and hashCode() overriden.



And now the view, it's Facelets, save it as crud.xhtml:





xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">

</span>Really simple CRUD<span class="codetag">


List items


rendered="#{not empty bean.list}">
value="#{bean.model}" var="item">
name="header">ID#{item.id}
name="header">Value#{item.value}
value="edit" action="#{bean.edit}" />
value="delete" action="#{bean.delete}" />


rendered="#{empty bean.list}">

Table is empty! Please add new items.



rendered="#{!bean.edit}">

Add item



Value: value="#{bean.item.value}" />


value="add" action="#{bean.add}" />




rendered="#{bean.edit}">

Edit item #{bean.item.id}



Value: value="#{bean.item.value}" />


value="save" action="#{bean.save}" />








Amazingly simple, isn't it? If you know or have read the well known Using Datatables article (it has been almost exactly 4 year ago when I wrote it for first! according to Google Analytics, that page alone has already been viewed almost 150,000 times since 1 September 2007), you'll realize how hacky and verbose it could/would be when doing it in the request scope alone with all of those bound components and reloading the data in the action method or getter.



If you've studied the managed bean code closely, you'll also see that the javax.faces.model.DataModel is finally parameterized in JSF 2.0. No need for nasty casts on getRowData() anymore.



Really simple CRUD, now without DataModel!



If you're targeting a Servlet 3.0 / EL 2.2 capable container such as Tomcat 7, Glassfish 3, JBoss AS 6, etc, then it can be done even more simple! You can pass method arguments in EL! This allows you for passing the current row just straight into bean's action method. Here's a minor rewrite of the above quick'n'dirty example which utilizes EL 2.2 powers.




package com.example;

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;

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

private List list;
private Item item = new Item();
private boolean edit;

@PostConstruct
public void init() {
// list = dao.list();
// Actually, you should retrieve the list from DAO. This is just for demo.
list = new ArrayList();
list.add(new Item(1L, "item1"));
list.add(new Item(2L, "item2"));
list.add(new Item(3L, "item3"));
}

public void add() {
// dao.create(item);
// Actually, the DAO should already have set the ID from DB. This is just for demo.
item.setId(list.isEmpty() ? 1 : list.get(list.size() - 1).getId() + 1);
list.add(item);
item = new Item(); // Reset placeholder.
}

public void edit(Item item) {
this.item = item;
edit = true;
}

public void save() {
// dao.update(item);
item = new Item(); // Reset placeholder.
edit = false;
}

public void delete(Item item) {
// dao.delete(item);
list.remove(item);
}

public List getList() {
return list;
}

public Item getItem() {
return item;
}

public boolean isEdit() {
return edit;
}

// Other getters/setters are actually unnecessary. Feel free to add them though.

}


And now the view:





xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">

</span>Really simple CRUD<span class="codetag">


List items


rendered="#{not empty bean.list}">
value="#{bean.list}" var="item">
name="header">ID#{item.id}
name="header">Value#{item.value}
value="edit" action="#{bean.edit(item)}" />
value="delete" action="#{bean.delete(item)}" />


rendered="#{empty bean.list}">

Table is empty! Please add new items.



rendered="#{!bean.edit}">

Add item



Value: value="#{bean.item.value}" />


value="add" action="#{bean.add}" />




rendered="#{bean.edit}">

Edit item #{bean.item.id}



Value: value="#{bean.item.value}" />


value="save" action="#{bean.save}" />








Note the action="#{bean.edit(item)}" and action="#{bean.delete(item)}". The current item is simply been passed as method argument! This allows us to get rid from DataModel altogether.



Back to top


Hey, there's "pitfalls" in the title?



Yes, well spotted. Check the following two questions on Stackoverflow.com:



In a nutshell: the @ViewScoped breaks when any UIComponent is bound to the bean using binding attribute or when using JSTL or tags in the view. In both cases the bean will behave like a request scoped one. The first one is in my opinion a pretty major bug, the second one is only an extra excuse to get rid of the whole JSTL stuff in Facelets.


This is related to JSF 2.0 issue 1492. Here's an extract of relevance:


This is a chicken/egg issue with partial state saving. The view is executed to populate the view *before* delta state is applied, so we see the behavior you've
described.

At this point, I don't see a clear way to resolve this use case.

The workaround, if you must use view-scoped bindings would be setting
javax.faces.PARTIAL_STATE_SAVING to false.



Back to top



Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.


(C) June 2010, BalusC



Source:http://balusc.blogspot.com/2010/06/benefits-and-pitfalls-of-viewscoped.html

Tidak ada komentar:

Posting Komentar