JSF Best Practices Tutorial
Hello, in this tutorial we will learn some important aspects of the best practices in JSF (i.e. we will talk about the scope and session management).
Table Of Contents
1. Introduction
Starting with JSF, one of the common pitfalls is how to pass values or parameters efficiently. Below are the different beans that are supported by JSF,
- Model Managed-Bean: Normally session scope – This type of managed-bean participates in the “Model” concern of the MVC design pattern. A JSF model-bean should be a POJO that follows the JavaBean design pattern with getters/setters encapsulating properties. The most common use case for a model bean is to be a database entity or to simply represent a set of rows from the result set of a database query.
- Backing Managed-Bean: Normally request scope – This type of managed-bean participates in the “View” concern of the MVC design pattern. The purpose of a backing bean is to support UI logic, and has a 1::1 relationship with a JSF view, or a JSF form in a Facelet composition. Although it typically has JavaBean-style properties with associated getters/setters, these are properties of the View. JSF backing beans may also have JSF ActionListener and ValueChangeListener methods.
- Controller Managed-Bean: Normally request scope – This type of managed-bean participates in the “Controller” concern of the MVC design pattern. The purpose of a controller bean is to execute some kind of business logic and return a navigation outcome to the JSF navigation handler.
- Support Managed-Bean: Normally session or application scope – This type of bean “supports” one or more views in the “View” concern of the MVC design pattern. The typical use case is supplying an ArrayList to JSF
h:selectOneMenu
drop-down lists that appear in more than one JSF view. If the data in the dropdown lists is particular to the user, then the bean would be kept in session scope. However, if the data applies to all users, then the bean would be kept in application scope so that it can be cached for all users. - Utility Managed-Bean: Normally application scope – This type of bean provides some type of “utility” function to one or more JSF views. A good example of this might be a FileUpload Bean that can be reused in multiple web applications.
However, in many cases, developers end up putting Managed Beans in Session Scope to share bean attributes though more appropriate solutions are available. This article lists some routine tasks for JSF developers and gives suggestions on how to pass values efficiently without having to pollute the session object.
1.1 Session Degradation & Why It’s Dangerous
Putting all your Managed Beans in Session Scope may be a working solution for some problems encountered while developing web applications with JSF. But it has an undesired behavior, which is mostly not noticed until real problems arise. For e.g.:
- Is the instantiated object required available throughout the entire user session?: The session spans over all requests and may involve traversing over your whole application.
- Is your code thread-safe?: The Session object is shared among all threads initiated by the same user. This may cause problems when objects are accessed concurrently e.g. when the user opens a second browser tab of the application.
- Larger session objects often affected the performance of the system as they require more physical memory.
With these points in mind let’s take a look at common scenarios in JSF development.
2. Passing information to handlers
Consider an example where we have a single Managed Bean and a command button triggering an action method. The points to consider are:
- How does the action method know where the command button was triggered?
- How does the context is passed on to the method?
A simple example:
Regardless which one of the h:commandButton
is pressed, they are all triggering the same action method. The method contains some logic to process the respective set of data. But how exactly does the method know which set of data it is to process? Let’s take a look at the view definition for this example:
View Definition for Example 1
... <h:form> <h1><h:outputText value="Example 1"/></h1> <h:dataTable value="#{adressTableBeanExample1.addresses}" var="address"> <h:column> <f:facet name="header" > <h:outputText value="Name"/> </f:facet> <h:outputText value="#{address.name}" /> </h:column> <h:column> <f:facet name="header" > <h:outputText value="Street"/> </f:facet> <h:outputText value="#{address.street}" /> </h:column> <h:column> <f:facet name="header" > <h:outputText value="ZIP"/> </f:facet> <h:outputText value="#{address.zipCode}" /> </h:column> <h:column> <f:facet name="header" > <h:outputText value="City"/> </f:facet> <h:outputText value="#{address.city}" /> </h:column> <h:column> <h:commandButton value="delete" action="#{adressTableBeanExample1.delete}"/> </h:column> </h:dataTable> </h:form> ...
This is a standard implementation in every jsf implemented web-application. The following section discusses some suggestions to solve the above-described problem.
2.1 Setting a property on the Managed Bean
The basic solution is to fill a certain property in the Managed Bean. Managed Bean may contain a property named “selected” to hold the data that was selected by the user upon clicking the h:commandButton
.
Since JSF 1.x, a tag is provided to the developers for this approached called f:setPropertyActionListener
. When using the f:setPropertyActionListener
component, Action Listener fills the targeted value of the Managed Bean with a given value.
The above example using a f:setPropertyActionListener
looks like this:
View Definition for Example 1 using f:setPropertyActionListener
... <h:column> <h:commandButton value="delete" action="#{adressTableBeanExample1.delete}" > <f:setPropertyActionListener target="#{adressTableBeanExample1.selected}" value="#{address}" /> </h:commandButton> </h:column> ...
With the implicitly created Action Listeners filling the property in the Managed Bean, all you need to do to provide availability of the selected data in the Action Method is to simply access the beans “selected” property:
Action Method in Backing Bean for Example 1
... public String delete(){ addresses.remove(selected); return ""; } ...
Although very simple and handy this solution has some drawbacks:
- Selected values need to have the same types
- With lots of different selections on the same page, Managed Bean may grow quickly resulting in reduced cohesion and thus, poor maintainability
However, developers can only use this approach unless the view is not complex and single selection is all developer’s need.
2.2 Adding parameters to the command component
The following approach eliminates the drawbacks of the prior one, by introducing parameters to the command component. While f:setPropertyActionListener
requires to be placed within a component derived from ActionSource, every component derived from UIComponent
is capable of carrying parameters.
By adding the selected dataset as a parameter to the h:commandButton
, this parameter is available whenever you are dealing with this component. Compared to Action Methods, Action Listeners are aware of the ActionEvent that triggered the action. Action Events refer to the component triggering the event, in this case, the h:commandButton
View Definition for Example 2 using f:param
... <h:column> <h:commandButton value="delete" actionListener="#{addressTableBeanExample2.delete}"> <f:param name="selected" value="#{address}" /> </h:commandButton> </h:column> ...
Having access to the component, all parameters are accessible by calling the components getChildren()
method to access the nested components
ActionListener in Backing Bean for Example 2
... public void delete(ActionEvent event){ for(UIComponent component : event.getComponent().getChildren()){ if( component instanceof UIParameter ){ UIParameter param = (UIParameter) component; if(param.getName().equals("selected")){ addresses.remove(param.getValue()); } } } } ...
As you can see in this approach, Action Method from the previous example has changed to an Action Listener. Since the h:commandButton
may contain multiple parameters, the Listener is responsible for checking for the parameter name to avoid evaluation of wrong parameters.
Although it’s a simple approach, this solution has again some drawbacks:
- Developers need to write some logic to traverse components children and check for the correct type of the nested child components as well.
- When dealing with multiple parameters developers need to distinguish them by name, which requires additional code.
- Since Action Listeners are used quite frequently this approach is common in Ajax-intensive applications.
2.3 Adding attributes to the command component
A convenient approach, due to the reduction of required code is to add the desired dataset as an attribute to the component instead of nesting it as a child parameter component.
When adding objects as attributes to components these objects are available through the component’s attribute map. The following code shows the above example using f:attribute
instead of f:param
:
View Definition for Example 3 using f:attribute
... <h:column> <h:commandButton value="delete" actionListener="#{addressTableBeanExample3.delete}" > <f:attribute name="selected" value="#{address}" /> </h:commandButton> </h:column> ...
Action Listener in Backing Bean for Example 3
... public void delete(ActionEvent event){ Address selected = (Address) event.getComponent().getAttributes().get("selected"); addresses.remove(selected); } ...
The main differences between usage of f:attribute
and f:param
is that parameters are added to the closest UIComponent associated with a custom action, requiring f:param
to be placed as child component somewhere underneath a component implementing the ActionSource interface.
Compared to the previous example, Listener in this example simply assumes that the parameter named “selected” is an instance of the Address class.
2.4 Passing values in tables
So far the concepts for passing values to handlers were applied within h:dataTables
. Passing values to handlers are easily achieved by placing a command component in a table column. Clicks on the command component may trigger an Action Method or Action Listener, the information about which data row to process may be passed as attribute or parameter.
The following code shows an example of an ActionListener using an Attribute to describe the selected table row. Let’s take a look at the view definition for this example:
View Definition for Example 4 using an ActionListener with f:attribute
... <h:dataTable value="#{addressTableBeanExample4.data}" var="data"> <h:column id="firstname"> <f:facet name="header"> <h:outputText value="Firstname"/> </f:facet> <h:outputText value="#{data.firstname}" /> </h:column> <h:column id="lastname"> <f:facet name="header"> <h:outputText value="Lastname" /> </f:facet> <h:outputText value="#{data.lastname}" /> </h:column> <h:column id="customerId"> <f:facet name="header"> <h:outputText value="Customer ID" /> </f:facet> <h:outputText value="#{data.customerId}" /> </h:column> <h:column id="action"> <h:commandButton value="Select" actionListener="#{addressTableBeanExample4.selectionListener}"> <f:attribute name="selection" value="#{data}"/> </h:commandButton> </h:column> </h:dataTable> ...
Backing Bean Definition for Example 4
... @ViewScoped @ManagedBean(name="addressTableBeanExample4") public class ExampleBean4 implements Serializable { private static final long serialVersionUID = 1L; private Customer selected; private transient List data = new ArrayList() ; public ExampleBean4(){ /* Creating Some Dummy Data For The Table */ data.add(new Customer("Homer","Simpson",80085)); data.add(new Customer("Barney","Gumble",83321)); data.add(new Customer("Ned","Flanders",81813)); } public void selectionListener(ActionEvent event){ Customer customer = (Customer) event.getComponent().getAttributes().get("selection"); this.selected = customer; } public Customer getSelected() { return selected; } public void setSelected(Customer selected) { this.selected = selected; } public List getData() { return data; } public void setData(List data) { this.data = data; } }
While the previous example requires explicit definition of an f:ActionListener
, JSF offers a more data-centric approach using a distinct data model for DataTables. The preceding example used a value binding to a collection containing the data to display. Using a reference to a DataModel instance instead of a collection offers a more convenient way to access the selected data set.
Let’s update the view definition for this:
View Definition for Example 5
... <h:dataTable value="#{addressTableBeanExample5.data}" var="data"> <h:column id="firstname"> <f:facet name="header"> <h:outputText value="Firstname"/> </f:facet> <h:outputText value="#{data.firstname}" /> </h:column> <h:column id="lastname"> <f:facet name="header"> <h:outputText value="Lastname" /> </f:facet> <h:outputText value="#{data.lastname}" /> </h:column> <h:column id="customerId"> <f:facet name="header"> <h:outputText value="Customer ID" /> </f:facet> <h:outputText value="#{data.customerId}" /> </h:column> <h:column id="action"> <h:commandButton value="Select" action="#{addressTableBeanExample5.select}"/> </h:column> </h:dataTable> ...
Backing Bean using a DataModel in Example 5
... @ViewScoped @ManagedBean(name="addressTableBeanExample5") public class ExampleBean5 implements Serializable { private static final long serialVersionUID = 1L; private transient ListDataModel data = new ListDataModel() ; private Customer selected; public ExampleBean5() { /* Creating Some Dummy Data For The Table */ List customers = new ArrayList(); customers.add(new Customer("Homer","Simpson",80085)); customers.add(new Customer("Barney","Gumble",83321)); customers.add(new Customer("Ned","Flanders",81813)); this.data.setWrappedData(customers); } public Customer getSelected() { return selected; } public void setSelected(Customer selected) { this.selected = selected; } public ListDataModel getData() { return data; } public void setData(ListDataModel data) { this.data = data; } public String select(){ this.selected = data.getRowData(); return ""; } }
As you can see in the example above, JSF takes care of informing the data model which distinct data set was selected. When an ActionSource is triggered, JSF takes notice about the related element of the wrapped data and updates the table model. Accessing the selected data set is made easy using the getRowData()
method of the TableModel
.
3. Sharing Information between Views
3.1 Action Listener
The usage of f:setPropertyActionListener
is a convenient way to store values in Managed Beans of subsequent views. However, though more code intensive, the same effect can be achieved by manually designed ActionListener.
This approach gives you the ability to process values before storing them in another Managed Bean. However, this may also tempt to put logic in the Presentation Layer that belongs elsewhere. Keep in mind, that JSF offers concepts for conversation and validation whenever you think about data transformation when passing values to managed beans using an Action Listener.
3.2 Flash
With JSF2 a new feature called “Flash” was introduced. It offers a convenient way to pass information between views. Though often mistakenly referred to as Flash Scope“, the Flash is not a scope like a request or session scope. It is rather a map managed by the framework.
It is capable of holding a value until the next view is processed, so you would not want to put an entire managed bean in the flash scope. The following example shows how the Flash can be used to pass an input from one view to another. There is a request scoped backing bean for the second view only.
Note: The Flash is injected in the backing bean using the @ManagedProperty
annotation. You could also access the Flash programmatically by calling FacesContext.getCurrentInstance().getExternalContext().getFlash()
but having the Flash injected into the bean is more convenient.
View Definition for first view in Example 6
... <h:form id="form"> ... <h:outputText value="Enter a value into the Flash"/> <h:inputText value="#{flash.inputText}" /> <h:commandButton value="submit" action="example6b.xhtml" /> </h:form> ...
View Definition for 2nd view in Example 6
... <h:form id="form"> ... <h:outputText value="Value From Flash:"/> <h:outputText value="#{flashExampleBean.inputFromFlash}" /> <h:commandButton value="back" action="example6a.xhtml" /> </h:form> ...
Backing Bean Definition for 2nd view in Example 6
@ManagedBean @RequestScoped public class FlashExampleBean implements Serializable { private static final long serialVersionUID = -4401270348003254611L; @ManagedProperty("#{flash}") private Flash flash; public String getInputFromFlash(){ String inputText = (String) flash.get("inputText"); flash.keep("inputText"); return inputText; } public void setFlash(Flash flash) { this.flash = flash; } public Flash getFlash() { return flash; } }
Developers may notice the call to flash.keep()
in the backing beans getter method. This tells the flash to keep the value for another subsequent request as values stored in the Flash during request N are only available throughout the request N+1 unless the Flash is told to keep it for another request. By calling flash.keep()
program assure that the input is still available when returning to the first page by pressing the back button.
4. When to use Session-Scope?
This article suggests several methods for passing values to handlers and between views without bloating the session. As told in the article beginning, objects stored in session scope remain until the end of the user session or until they are removed programmatically. A common scenario where an object is used throughout the entire lifetime of the user session is authentication.
Consider a login screen where the user has to enter its credentials. If authentication is successful the user session is associated with an object representing the authenticated user. It may contain the users’ name, its customer id etc. This object may be used throughout the entire session to decide for example the look and feel of the application, the options given to the user and the general behavior of the application for this particular user.
It is vital to have a session scoped managed bean in your JSF application that stores information needed throughout the user session, but it is good practice to have only one session bean.
5. Conclusion
The main goal of this article is to discuss common scenarios of improperly session scoped beans and to give advice on how to prevent this. Of course, all this does not mean that session scope is a bad thing. The usage of session scoped beans may be totally valid.
However, developers need to be sure that their intended solution is not only working on a single use case but also free from side effects. This article hopefully helps to shed some light on the side effects of improper usage of session scopes.
6. Tips
Below are certain techniques which can be taken into consideration as a part of JSF best practices,
- To keep your application scalable, minimize creating managed beans in session scope. It solves the component state problem but it’s a serious issue if the application is hosted in a clustered environment.
- Customize default JSF error messages.
- Use Facelets layout feature to simulate Struts Tiles behavior.
- Use
h:outputLink
for simple page navigations andh:commandLink
for form submissions. - If developers use JSF1.2, then use
f:view beforePhase=”#{method-binding}”
to execute the page initialization or stuffs like fetching data from the database, or creating error messages which are to be displayed. - Don’t use
c:if
inside iterative tags likeh:datatable
andui:repeat
. For reason, click here - If you’re using JSF1.2, use field-specific custom error message wherever is required.
7. Download the Eclipse Project
This was an example of JSF Best Practices.
You can download the full source code of this example here: JSF BestPractices
I recently started learning JSF and regarding passing information to handlers I’ve seen the approach to simply pass the variable as a method parameter.
Eg.: https://pastebin.com/BCBHU6Eb
(I had to post the code to pastebin because I couldn’t figure out how to paste code without it getting ignored – does anybody know?)
Is it and wrong to use this approach? If so, why?
Thanks in advance!