JSF Scopes Tutorial – JSF/CDI Session Scope
The session scope lives across multiple HTTP request-response cycles (theoretical unlimited).
The request scope is very useful in any web application when you need a single interaction per HTTP request-response cycle. However, when you need objects visible for any HTTP request-response cycle that belongs to a user session, then you need a session scope; in this case, the bean lives as long as the HTTP session lives. The session scope allows you to create and bind objects to a session. It gets created upon the first HTTP request involving this bean in the session and gets destroyed when the HTTP session is invalidated. The request scope is present in JSF and CDI and functions in the same way. It can be used for non-rich AJAX and non-AJAX requests.
Session Scope Annotations
JSF: The JSF request scope annotation is @SessionScoped
(javax.faces.bean.SessionScoped
). A bean with this scope should be annotated with @ManagedBean
(javax.faces.bean.ManagedBean
). The default scope is @RequestScope
.
CDI: The CDI request scope annotation is @SessionScoped
(javax.enterprise.context.SessionScoped
). 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.SessionScoped; // for CDI import javax.inject.Named; import javax.enterprise.context.SessionScoped; // JSF vs CDI @ManagedBean @Named @SessionScoped @SessionScoped 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, via forward mechanism back in the same view (or another view) or redirect mechanism the count
value will be increased by 1
. This reveals two aspects:
- The
CountBean
constructor is called for creating a new instance once per user session. This means thatcount
is initialized with0
only once. Further requests fired in the current user session will use thisCountBean
instance. We say that there is aCountBean
instance per user. - The session scope doesn’t lose the object’s state while forwarding or redirecting. The object’s state is available until the
session
is destroyed (e.g. session timeout, invalidate, etc).
Basically you have to pay attention when you submit data to a session scoped bean. The submitted data will “live” as long as the current user session. So, a good practice will tell you to not store in session large amount of data, especially if memory is a critical resource.
Implements Serializable
JSF and CDI managed beans should be declared Serializable
(implements Serializable
). This is needed because container may persist (serialize) session 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 sessions during server restart.
Session Scope Programmatic Access
Programmatically you can interact with session scope like this:
- access the session scope map
// JSF 2.0-2.2 FacesContext context = FacesContext.getCurrentInstance(); Map<String, Object> requestMap = context.getExternalContext().getSessionMap(); // JSF 2.3 @Inject @SessionMap private Map<String, Object> sessionMap; // OmniFaces Map<String, Object> requestmap = Faces.getSessionMap();
- get a session scoped attribute
// JSF 2.0 - 2.3 sessionMap.put(name, value); // OmniFaces Faces.setSessionAttribute(name, value);
- remove a session scoped attribute
// JSF 2.0-2.3 Object value = sessionMap.remove(name); // OmniFaces <T> value = Faces.removeSessionAttribute(name);
! In JSF pages, you can use the implicit object, #{sessionScope}
(e.g. get CountBean
instance: #{sessionScope.countBean}
).
Among others, the session map will contain instances of managed beans that are declared under the session scope (@SessionScoped (JSF/CDI
)).
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 session map. Therefore you will be able to locate an instance of this JSF managed bean in the session 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 session map. So, via the session map, you can access a property of a session scoped JSF managed bean, like this:
String count = ((CountBean)(Faces.getSessionAttribute("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.getSessionAttribute(bean_name))).getCount();
Now, you can easily intuit how to work with managed beans stored in the session 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 session scoped beans the method annotated with @PostConstruct
will be called only once, after the session scoped bean instance was created.
JSF managed bean example:
import java.io.Serializable; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; @ManagedBean @SessionScoped 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.SessionScoped; @ManagedBean @SessionScoped 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.enterprise.context.SessionScoped; import javax.inject.Named; @Named @SessionScoped 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.enterprise.context.SessionScoped; import javax.inject.Named; @Named @SessionScoped 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 session scoped beans
JSF: For JSF managed beans, injection is accomplished via @ManagedProperty
. For example:
CDI: For CDI managed beans, injection is accomplished via @Named
. 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 session scoped managed bean you can inject session and application managed beans, but not request or view 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 session 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 session scoped managed beans programmatically
Starting with JSF 2.2, we can programmatically reproduce the content of faces-config.xml
. For session 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("session")); 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 request 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>session</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 JSF/CDI application scope.
Reference: | JSF Scopes Tutorial – JSF/CDI Session Scope from our JCG partner Anghel Leonard at the JSF and OmniFaces Fans blog. |
Dear Mr Leonard ,
Just want to say thank you very much for this article, it was extremely helpful to me. I hadn’t used @Named in my now session Managed Beans…@Inject Now works!
Excellent article!