My JSF Books/Videos My JSF Tutorials OmniFaces/JSF PPTs
JSF 2.3 Tutorial
JSF Caching Tutorial
JSF Navigation Tutorial
JSF Scopes Tutorial
JSF Page Author Beginner's Guide
OmniFaces 2.3 Tutorial Examples
OmniFaces 2.2 Tutorial Examples
JSF Events Tutorial
OmniFaces Callbacks Usages
JSF State Tutorial
JSF and Design Patterns
JSF 2.3 New Features (2.3-m04)
Introduction to OmniFaces
25+ Reasons to use OmniFaces in JSF
OmniFaces Validators
OmniFaces Converters
JSF Design Patterns
Mastering OmniFaces
Reusable and less-verbose JSF code

My JSF Resources ...

Java EE Guardian
Member of JCG Program
Member MVB DZone
Blog curated on ZEEF
OmniFaces is an utility library for JSF, including PrimeFaces, RichFaces, ICEfaces ...

[OmniFaces Utilities] - Find the right JSF OmniFaces 2 utilities methods/functions

Search on blog

Petition by Java EE Guardians

Twitter

marți, 3 noiembrie 2015

JSF Scopes Tutorial - JSF/CDI View Scope

The view scope lives as long as you are navigating in the same JSF 
view in the browser window/tab.

The view scope is useful when you need to preserve data over multiple requests without leaving the current JSF view by clicking on a link, returning a different action outcome, or any other interaction that dumps the current view. It gets created upon an HTTP request and gets destroyed when you postback to a different view; as long as you postback to the same view, the view scope is alive. Since the view scope is particularly useful when you are editing some objects while staying in the same view, it can be the perfect choice for rich AJAX requests. Moreover, since the view scope is bounded to the current view, it does not reflect the stored information in another window or tab of a browser; this is an issue specific to
the session scope!

! In order to keep the view active, the bean methods (actions/listeners) must return null or void.

View Scope Annotations
JSF: The JSF view scope annotation is @ViewScoped  (javax.faces.bean.ViewScoped). A bean with this scope should be annotated with @ManagedBean (javax.faces.bean.ManagedBean). The default scope is @RequestScope.

CDI: The view scope is not available in CDI, but JSF 2.2 has introduced it through the new annotation, @ViewScoped. This is defined in the javax.faces.view.ViewScoped package and it is compatible with CDI. Do not confuse this @ViewScoped with the one defined in the javax.faces.bean package, which is JSF compatible! So, the CDI view scope annotation is @ViewScoped  (javax.faces.view.ViewScoped). A bean with this scope should be annotated with @Named (javax.inject.Named). For CDI managed beans (@Named), the default scope is the @Dependent pseudo-scope.

Simple Example
// index.xhtml
<h:body>  
 <h4>Same view after submit (index.xhtml):</h4>
 <h:form>
  <h:commandButton value="Count" action="#{countBean.countActionVoid()}"/>
 </h:form>
 Current value: #{countBean.count}

 <h4>Forward to another view after submit (count.xhtml):</h4>
 <h:form>
  <h:commandButton value="Count" action="#{countBean.countActionAndForward()}"/>
 </h:form>
 Current value: #{countBean.count}

 <h4>Redirect to another view after submit (count.xhtml):</h4>
 <h:form>
  <h:commandButton value="Count" action="#{countBean.countActionAndRedirect()}"/>
 </h:form>
 Current value: #{countBean.count}

 <h4>AJAX :</h4>
 <h:form>
  <h:commandButton value="Count" action="#{countBean.countActionVoid()}">
   <f:ajax render="currentValueId"/>
  </h:commandButton>
 </h:form>
 <h:outputText id="currentValueId" value="Current value: #{countBean.count}"/>
</h:body>

// count.xhtml
<h:body>           
 Current value: #{countBean.count}        
</h:body>

// CountBean.java
import java.util.logging.Logger;
import java.io.Serializable;
// for JSF
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
// for CDI
import javax.inject.Named;
import javax.faces.view.ViewScoped;

// JSF            vs            CDI
@ManagedBean                    @Named
@ViewScoped                     @ViewScoped
public class CountBean implements Serializable {

 private static final Logger LOG = Logger.getLogger(CountBean.class.getName());

 private int count;

 public CountBean() {
  LOG.info("CountBean#Initializing counter ...");
  count = 0;
 }

 public void countActionVoid() {
  LOG.info("CountBean#countActionVoid() - Increasing counter ...");
  count++;
 }
   
 public String countActionAndForward() {
  LOG.info("CountBean#countActionAndForward() - Increasing counter ...");
  count++;
  return "count";
 }
   
 public String countActionAndRedirect() {
  LOG.info("CountBean#countActionAndRedirect() - Increasing counter ...");
  count++;
  return "count?faces-redirect=true;";
 }

 public int getCount() {
  return count;
 }

 public void setCount(int count) {
  this.count = count;
 } 
}

The complete application is available here.

So, when navigating via AJAX requests or via implicit forward mechanism back in the same view then the count value will be increased by 1. Navigating via redirect or explicit forward (including explicit navigation to the current view)  will reset/reinitialize the count. This reveals two aspects:

- The CountBean constructor is called for creating a new instance once per view.  This means that count is initialized with 0 only once. Further requests fired in the current view will use this CountBean instance. We say that there is a CountBean instance per view.
- The view scope doesn't lose the object's state while forwarding to the same view (except the case when this is done explicitly from page or a managed bean method return). The object's state is available until the view is destroyed (e.g. expires) or you navigate to another view/window/tab or explicitly to the same view/tab/window.

Basically you have to pay attention when you submit data to a view scoped bean. The submitted data will "live" as long as the current view, and will not "live" across views.

Implements Serializable
JSF and CDI managed beans should be declared Serializable (implements Serializable). This is needed because container may persist (serialize) view data to hard disk. This allows container to manage critical situations as heavy loading, or simply share data with other servers in a cluster or to revive views during server restart.

View Scope Pitfall - JSF 2.2 - the view scoped beans and the stateless feature pitfall
In a stateless environment, the view scoped beans act as request scoped beans. Besides the fact that you can't create/manipulate views dynamically, this is one of the big disadvantages that comes with the stateless feature, because it will affect AJAX-based applications that usually use view scoped beans. You can easily test this behavior with a set of beans with different scopes. The view scoped bean can be defined as follows:

@Named
@ViewScoped
public class TimestampVSBean implements Serializable{

 private static final long serialVersionUID = 1L;

 private Timestamp timestamp;

 public TimestampVSBean() {
  java.util.Date date = new java.util.Date();
  timestamp = new Timestamp(date.getTime());
 }

 public Timestamp getTimestamp() {
  return timestamp;
 }

 public void setTimestamp(Timestamp timestamp) {
 this.timestamp = timestamp;
 }
}

Just change the scope to request, session, and application to obtain the other three beans.
Next, we will write a simple stateless view as follows:

<f:view transient="true">
 <h:form>
  <h:commandButton value="Generate Timestamp"/>
 </h:form>

 Request Scoped Bean:
 <h:outputText value="#{timestampRSBean.timestamp}"/>

 View Scoped Bean:
 <h:outputText value="#{timestampVSBean.timestamp}"/>
 [keep an eye on this in stateless mode]

 Session Scoped Bean:
 <h:outputText value="#{timestampSSBean.timestamp}"/>

 Application Scoped Bean:
 <h:outputText value="#{timestampASBean.timestamp}"/>
</f:view>

Afterwards, just submit this form several times (click on the Generate Timestamp button) and notice that the timestamp generated by the view scoped bean changes at every request. This is a JSF pitfall!

The request, session, and application scopes work as expected!

For more view scope pitfalls, you must read: The benefits and pitfalls of @ViewScoped, by Bauke Scholtz (aka BalusC)

View Scope Programmatic Access
Programmatically you can interact with view scope like this:

- access the view scope map

// JSF 2.0-2.2
FacesContext context = FacesContext.getCurrentInstance();
Map<String, Object> viewMap = context.getViewRoot().getViewMap();

// JSF 2.3
@Inject
@ViewMap
private Map<String, Object> viewMap;

Map<String, Object> viewMap = Faces.getViewMap();

- set a view scoped attribute

// JSF 2.0 - 2.3
viewMap.put(name, value);

Faces.setViewAttribute(name, value);

- get a view scoped attribute

// JSF 2.0-2.3
Object value = viewMap.get(name);

<T> value = Faces.getViewAttribute(name);

- remove a view scoped attribute

// JSF 2.0-2.3
Object value = viewMap.remove(name);

<T> value = Faces.removeViewAttribute(name);

! In JSF pages, you can use the implicit object, #{viewScope} (e.g. get CountBean instance: #{viewScope.countBean}).

Among others, the view map will contain instances of managed beans that are declared under the view scope (@ViewScoped (JSF/CDI)). Practically, the view scope is stored in the session scope.

In case of JSF managed beans (not CDI managed beans - in this case, the keys are pretty complex), you can easily identify such beans by their names which becomes keys in the view map. Therefore you will be able to locate an instance of this JSF managed bean in the view map under the key, countBean. If you specify the bean name via @ManagedBean(name="some_name"), then some_name will be the key in the view map. So, via the view map, you can access a property of a view scoped JSF managed bean, like this:

String count = ((CountBean)(Faces.getViewAttribute("countBean/some_name"))).getCount();

Is perfectly legal to do this also (this refers to the current bean):

@ManagedBean(name="some_name")
...
String bean_name = getClass().getAnnotation(ManagedBean.class).name();
int count = ((CountBean)(Faces.getViewAttribute(bean_name))).getCount();

Now, you can easily intuit how to work with managed beans stored in the view map.

Using @PostConstruct
Typically, in a managed bean, we need to write a method annotated with @PostConstruct for accomplishing initializations tasks based on injected artifacts.  With other words, the @PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization. When the initialization doesn't involve injected artifacts the constructor can be used for initializations. For view scoped beans the method annotated with @PostConstruct will be called only once, after the view scoped bean instance was created (the constructor and @PostConstruct will be invoked once per view creation).

JSF managed bean example:

import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ManagedBean
@ViewScoped
public class InitBean implements Serializable{

 private int init;

 public InitBean() {
  init = 5;
 }

 public int getInit() {
  return init;
 }

 public void setInit(int init) {
  this.init = init;
 }
}

import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ManagedBean
@ViewScoped
public class CountBean implements Serializable {
   
 @ManagedProperty("#{initBean}")
 private InitBean initBean;

 @PostConstruct
 public void init(){
  LOG.info("CountBean#Initializing counter with @PostConstruct ...");
  count = initBean.getInit();
 }

 public void setInitBean(InitBean initBean) {
  this.initBean = initBean;
 } 
 ...
}

CDI managed bean example:

import java.io.Serializable;
import javax.faces.view.ViewScoped;
import javax.inject.Named;

@Named
@ViewScoped
public class InitBean implements Serializable {

 private int init;

 public InitBean() {
  init = 5;
 }

 public int getInit() {
  return init;
 }

 public void setInit(int init) {
  this.init = init;
 }
}

import java.io.Serializable;
import javax.inject.Inject;
import javax.faces.view.ViewScoped;
import javax.inject.Named;

@Named
@ViewScoped
public class CountBean implements Serializable {
   
 @Inject
 private InitBean initBean;

 @PostConstruct
 public void init(){
  LOG.info("CountBean#Initializing counter with @PostConstruct ...");
  count = initBean.getInit();
 }
 ...
}

Injection and view scoped beans
JSF: For JSF managed beans, injection is accomplished via @ManagedProperty. For example:



CDI: For CDI managed beans, injection is accomplished via @Inject. For example:



JSF & CDI mixed: CDI can be injected in JSF (vice versa is not true!)



JSF Managed Beans Restrictions:
! As a general rule in JSF, don't use objects that have shorter lifespan than the objects you are
calling it from. In other words, use objects whose lifespan is the same as, or longer than, the object being injected into. Breaking this rule will end up in a JSF exception. Base on this rule in a JSF view scoped managed bean you can inject view, session and application managed beans, but not request managed beans.

JSF managed beans can be injected in other JSF managed beans.

CDI Managed Beans Restrictions:
! When you are using an object that has a shorter lifespan than the object you are calling it from (for example, injecting a request scoped bean into a view scoped bean), CDI classifies the use case as a mismatched injection and fixes the issue via CDI proxies. For each request, the CDI proxy re-establishes the connection to a live instance of the request scoped bean.

CDI managed beans can be injected in JSF managed beans.

Configuring JSF view scoped managed beans programmatically
Starting with JSF 2.2, we can programmatically reproduce the content of faces-config.xml.  For view scoped managed beans, the relevant snippet of code is:

@Override
public void populateApplicationConfiguration (Document toPopulate) {

 String ns = toPopulate.getDocumentElement().getNamespaceURI();

 Element managedbeanEl = toPopulate.createElementNS(ns, "managed-bean");

 Element managedbeannameEl = toPopulate.createElementNS(ns, "managed-bean-name");
 managedbeannameEl.appendChild(toPopulate.createTextNode("countBean"));
 managedbeanEl.appendChild(managedbeannameEl);

 Element managedbeanclassEl = toPopulate.createElementNS(ns, "managed-bean-class");
 managedbeanclassEl.appendChild(toPopulate.createTextNode("beans.CountBean"));
 managedbeanEl.appendChild(managedbeanclassEl);

 Element managedbeanscopeEl = toPopulate. createElementNS(ns, "managed-bean-scope");
 managedbeanscopeEl.appendChild(toPopulate. createTextNode("view"));
 managedbeanEl.appendChild(managedbeanscopeEl);
 ...
 // programmatic create managed-property
 ...
 toPopulate.getDocumentElement().appendChild(managedbeanEl);
}

A complete application can be seen in Mastering JSF 2.2 book .

Configuring JSF view scoped managed beans in XML file
With XML configuration, you can use the old JSF 1.x mechanism to define the managed bean in a normal faces-config.xml file. For example:

...
<managed-bean>
 <managed-bean-name>countBean</managed-bean-name>
 <managed-bean-class>beans.CountBean</managed-bean-class>
 <managed-bean-scope>view</managed-bean-scope>
 ...
 <!-- managed bean properties --> via <managed-property/>
 ...
</managed-bean>
...

Managed beans should be defined in a separate XML file because the faces-config.xml is used to set the application level configurations. Basically, if you prefer this approach, create a new XML file and put the managed beans detail inside. Finally, declare the XML file via javax.faces.CONFIG_FILES context parameter in web.xml file.

...
<context-param>
 <param-name>javax.faces.CONFIG_FILES</param-name>
 <param-value>WEB-INF/my-manage-beans.xml</param-value>
</context-param>
...

See you in the next post about OmniFaces view scope.

Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

OmniFaces/JSF Fans

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors