• cdi
  • components
  • contexts
  • converters
  • eventlisteners
  • exceptionhandlers
  • facesviews
  • filters
  • functions
  • managedbeans
  • push
  • resourcehandlers
  • scripts
  • servlets
  • taghandlers
  • utils
  • validators
  • viewhandlers
-
  • ContextParam
  • Cookie
  • Eager
  • FacesConverter
  • FacesValidator
  • GraphicImageBean
  • Param
  • ViewScoped

The CDI view scope annotation, with more optimal handling of bean destroy as compared to standard JSF one.

In standard JSF 2.0/2.1, the @PreDestroy annotated method on a view scoped bean was never invoked when the session expires. Since OmniFaces 1.6, this CDI view scope annotation will guarantee that the @PreDestroy annotated method is also invoked on session expire. Since JSF 2.2, this problem is solved on native JSF view scoped beans, hereby making this annotation superflous in JSF 2.2.

However, there may be cases when it's desirable to immediately destroy a view scoped bean as well when the browser unload event is invoked. I.e. when the user navigates away by GET, or closes the browser tab/window. None of the both JSF 2.2 view scope annotations support this. Since OmniFaces 2.2, this CDI view scope annotation will guarantee that the @PreDestroy annotated method is also invoked on browser unload. This trick is done by a synchronous XHR request via an automatically included helper script omnifaces:unload.js. There's however a small caveat: on slow network and/or poor server hardware, there may be a noticeable lag between the enduser action of unloading the page and the desired result. If this is undesireable, then better stick to JSF 2.2's own view scope annotations and accept the postponed destroy.

Since OmniFaces 2.3, the unload has been further improved to also physically remove the associated JSF view state from JSF implementation's internal LRU map in case of server side state saving, hereby further decreasing the risk at ViewExpiredException on the other views which were created/opened earlier. As side effect of this change, the @PreDestroy annotated method of any standard JSF view scoped beans referenced in the same view as the OmniFaces CDI view scoped bean will also guaranteed be invoked on browser unload.

In a nutshell: if you're on JSF 2.0/2.1, and you can't upgrade to JSF 2.2, and you want the @PreDestroy to be invoked on sesison expire too, then use OmniFaces 1.6+ with this view scope annotation. Or, if you're on JSF 2.2 already, and you want the @PreDestroy to be invoked on browser unload too, then use OmniFaces 2.2+ with this view scope annotation.

Related JSF issues:

Usage

Just use it the usual way as all other CDI scopes. Watch out with IDE autocomplete on import that you don't accidentally import standard JSF's own one.

import javax.inject.Named;
import org.omnifaces.cdi.ViewScoped;

@Named
@ViewScoped
public class OmniCDIViewScopedBean implements Serializable {}

Please note that the bean must implement Serializable, otherwise the CDI implementation will throw an exception about the bean not being passivation capable.

Under the covers, CDI managed beans with this scope are via ViewScopeManager stored in the session scope by an UUID based key which is referenced in JSF's own view map as available by UIViewRoot.getViewMap(). They are not stored in the JSF view map itself as that would be rather expensive in case of client side state saving.

Configuration

By default, the maximum number of active view scopes is hold in a LRU map with a default size equal to the first non-null value of the following context parameters:

If none of those context parameters are present, then a default size of 20 will be used. When a view scoped bean is evicted from the LRU map, then its @PreDestroy will also guaranteed to be invoked.

CDI issues in EAR

Note that CDI has known issues when OmniFaces is bundled in multiple WARs in a single EAR and the CDI feature is based on an Extension. It's important to understand that those issues are not related to OmniFaces, but to the broken CDI spec. For an overview of those issues, please refer Known issues of OmniFaces CDI features in combination with specific application servers.

Demo

CDI view scoped bean

Status:

  • It's now: Fri Jan 20 02:49:10 EST 2017
  • Session ID: IkL-gcxObSLAvPsecoQWwOOa
  • CDI view scoped bean: org.omnifaces.showcase.cdi.CdiViewScopedBean@123f72c

Messages from CDI view scoped bean:

  • PostConstruct invoked: org.omnifaces.showcase.cdi.CdiViewScopedBean@123f72c

JSF view scoped bean

Status:

  • It's now: Fri Jan 20 02:49:10 EST 2017
  • Session ID: IkL-gcxObSLAvPsecoQWwOOa
  • JSF view scoped bean: org.omnifaces.showcase.cdi.JsfViewScopedBean@19ed86c

Messages from JSF view scoped bean:

  • PostConstruct invoked: org.omnifaces.showcase.cdi.JsfViewScopedBean@19ed86c
Demo source code
<h3>CDI view scoped bean</h3>
<h:form id="cdiViewScopedForm">
    <p>Status:</p>
    <ul>
        <li>It's now: #{now}</li>
        <li>Session ID: #{session.id}</li>
        <li>CDI view scoped bean: #{cdiViewScopedBean}</li>
    </ul>
    <p>
        <h:commandButton value="submit form without ajax" action="#{cdiViewScopedBean.submit}" />
        <f:ajax execute="@form" render="@form :jsfViewScopedForm">
            <h:commandButton value="submit form with ajax" action="#{cdiViewScopedBean.submit}" />
            <h:commandButton value="rebuild view" action="#{cdiViewScopedBean.rebuildView}" />
            <h:commandButton value="navigate on POST" action="#{cdiViewScopedBean.navigate}" />
        </f:ajax>
        <h:button value="refresh page" />
    </p>
    <p>Messages from CDI view scoped bean:</p>
    <h:messages for="cdiViewScopedForm" />
</h:form>

<hr />

<h3>JSF view scoped bean</h3>
<h:form id="jsfViewScopedForm">
    <p>Status:</p>
    <ul>
        <li>It's now: #{now}</li>
        <li>Session ID: #{session.id}</li>
        <li>JSF view scoped bean: #{jsfViewScopedBean}</li>
    </ul>
    <p>
        <h:commandButton value="submit form without ajax" action="#{jsfViewScopedBean.submit}" />
        <f:ajax execute="@form" render="@form :cdiViewScopedForm">
            <h:commandButton value="submit form with ajax" action="#{jsfViewScopedBean.submit}" />
            <h:commandButton value="rebuild view" action="#{jsfViewScopedBean.rebuildView}" />
            <h:commandButton value="navigate on POST" action="#{jsfViewScopedBean.navigate}" />
        </f:ajax>
        <h:button value="refresh page" />
    </p>
    <p>Messages from JSF view scoped bean:</p>
    <h:messages for="jsfViewScopedForm" />
</h:form>