Reputation: 94
Each time I navigate to a page or perform an action, my CustomerController is injected multiple times. My CustomerController is Request Scoped so I understand that each request will mean a new instance is created and injected but what I'm seeing seems excessive to me.
For example, after I deploy my application (without errors) I navigate to the index.xhtml
page. From the index.xhtml
page I click on the 'Add Customers' link which takes me to the customer.xhtml
page. This results in 16 CustomerController injections by the time the customer.xhtml
page has loaded! The line
CustomerController.init called
(and associated output) appears 16 times. Since the init
method is annotated with @PostConstruct
it is called after the CustomerController is injected.
When I try to save a new customer I get 1 500 lines added to my log from the various println
calls I put in to help debug. Most of them however are repetitions because the controller is injected so many times.
Why is the CustomerController injected so many times?
Something I've noticed is that if I import the javax.enterprise.context.RequestScoped
class instead of the javax.faces.bean.RequestScoped
class then I get the single injection behaviour I'd expect. I guess this means the issue lies with JSF and (more likely) my use of JSF and not with the way I inject beans in general.
index.xhtml
<html xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<ui:composition template="templates/layout.xhtml">
<ui:define name="content">
<a href="customer.xhtml">Add Customer</a>
<a href="customerList.xhtml">Customers</a>
</ui:define>
</ui:composition>
</html>
customer.xhtml
<html xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<ui:composition template="templates/layout.xhtml">
<ui:define name="content">
<h3>
<h:outputText value="Customer Details" />
</h3>
<br />
<h:outputText value="* All fields are required" styleClass="errors" />
<br />
<h:form method="post" id="customerDetailsForm"
binding="#{customerController.component}">
<h:panelGrid columns="2" cellpadding="2" styleClass="frmTblSmall">
<h:outputText value="ID" styleClass="fieldName" />
<h:inputText id="username"
rendered="#{!customerController.editMode}"
value="#{customerController.customer.username}" required="true"
immediate="true" />
<h:outputText rendered="#{customerController.editMode}"
value="#{customerController.customer.username}" />
<h:outputText value="New Password" />
<h:inputSecret id="password"
value="#{customerController.customer.password}" required="true"
immediate="true" />
<h:outputLabel>
</h:outputLabel>
<h:outputText value="Status" />
<h:selectOneRadio id="customerStatus"
value="#{customerController.customer.active}"
styleClass="textBox" required="true"
immediate="true">
<f:selectItem itemValue="#{true}" itemLabel="Active" />
<f:selectItem itemValue="#{false}" itemLabel="Inactive" />
</h:selectOneRadio>
</h:panelGrid>
<h:panelGrid columns="1" styleClass="btnTblSmall">
<h:inputHidden id="formMode" value="#{customerController.editMode}">
</h:inputHidden>
<h:inputHidden rendered="#{customerController.editMode}"
value="#{customerController.hiddenUsername}" id="hiddenUsername"/>
<h:commandButton name="saveBtn" value="Save" styleClass="btn"
action="#{customerController.save()}">
</h:commandButton>
</h:panelGrid>
</h:form>
</ui:define>
</ui:composition>
</html>
CustomerController.java
/**
*
*/
package my.webapp.web.controller;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.bean.RequestScoped;
//import javax.enterprise.context.RequestScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import my.model.Customer;
import my.webapp.interfaces.DatabaseException;
import my.webapp.interfaces.ICustomerDao;
/**
* This is the controller class that will control and handle all request for the
* customer
*
* This bean is a request scoped bean which will be created when the JSF page is
* loaded and is destroyed when the user navigates to another page.
*
* The bean has @Named annotation so that the bean properties can be used in the
* UI
*
*/
@Named(value = "customerController")
@RequestScoped
public class CustomerController extends FormRequestController {
/**
* This is the bean class for the controller to get the selected data.This
* bean has a getter and setter method to assign value to the HTML data
* table that is used to allow the user to set/get an entity object to
* view/edit/delete
*/
@Inject
private HTMLDataTableActionBean htmlDataTableActionBean;
/**
* Data Access Object for customer
*/
@EJB
private ICustomerDao customerDao;
@Inject
private Customer customer;
private String hiddenUsername;
/**
* This method is executed to perform any initialisation. This method is
* called after dependency injection is done. This method must be invoked
* before the class is put into service. The customer list is
* created in post construction, otherwise JSF will retrieve an empty or a
* completely different list while processing the form submit and thus won't
* be able to locate the button pressed and won't invoke any action.
* @throws DatabaseException
*/
@PostConstruct
public void init() throws DatabaseException {
System.out.println("CustomerController.init called");
System.out.println("editMode:"+getEditMode());
customerDao.setType(Customer.class);
setEntityObjectList(findAll());
printParamMap();
if (null == customer)
{
System.out.println("customer is null");
customer = new Customer();
setEditMode(false);
}
else
{
System.out.println("customer.username: "+customer.getUsername());
System.out.println("customer.password: "+customer.getPassword());
}
System.out.println("CustomerController.init finished");
}
/**
*
*/
private void printParamMap()
{
System.out.println("Print request parameters");
Map<String, String> requestParameters = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
Set<Entry<String, String>> entries = requestParameters.entrySet();
for(Entry<String, String> entry : entries)
{
System.out.println("Key: '"+entry.getKey()+"'\nValue: '"+entry.getValue()+"'");
}
}
/*
* (non-Javadoc)
*
* @see my.webapp.web.contoller.FormRequestController#processRequest()
*/
public void processRequest(FormActionToPerform action) throws DatabaseException {
System.out.println("CustomerController.processRequest with action: "+action.toString());
switch (action) {
/*
* The following steps should be performed to display the data table
* view. fetch the customer list from the database. Always fetch
* from the database so that new customers are in the list as
* well
*/
case SHOW_VIEW_FOR_LIST:
setEntityObjectList(findAll());
break;
/*
* Get object selected to edit/view/delete
*/
case SHOW_EDIT_VIEW:
case SHOW_VIEW_TO_VIEW_SELECTED_OBJECT:
case SHOW_DELETE_VIEW:
{
Integer uniqueId = (Integer)getHtmlDataTableActionBean().getSelectedEntityObject().getUniqueId();
System.out.println("Selected entity's unique ID: "+uniqueId.toString());
customer = customerDao.read(uniqueId);
System.out.println("customer.username: "+customer.getUsername());
}
break;
default:
System.out.println("No request processing performed");
break;
}
System.out.println("CustomerController.processRequest finished");
}
/*
* (non-Javadoc)
*
* @see
* my.webapp.web.contoller.FormRequestController#doShowUIView(my.webapp
* .web.contoller.FormRequestController.FormActionToPerform)
*/
@Override
String doShowUIView(FormActionToPerform action) {
System.out.println("CustomerController.doShowUIView with action: "+action.toString());
String responseURL = HOME;
switch (action) {
case SHOW_EDIT_VIEW:
setEditMode(true);
setComponent(null);
responseURL = URL_CUSTOMER;
break;
default:
System.out.println("default condition");
responseURL = HOME;
}
System.out.println("CustomerController.doShowUIView returning URL: "+responseURL);
return responseURL;
}
@Override
public String save() {
System.out.println("CustomerController.save called");
System.out.println("editMode:"+getEditMode());
System.out.println("customer.username: "+customer.getUsername());
String URL = URL_CUSTOMER;
String password = customer.getPassword();
if (password != null)
{
System.out.println("saving new password");
String pw_hash = password;
customer.setPassword(pw_hash);
}
else
{
System.out.println("not saving new password");
}
try
{
customerDao.update(customer);
URL = HOME;
}
catch (Exception e)
{
setErrorMessage(e.toString());
}
return URL;
}
@Override
public String delete() {
return HOME;
}
public List<?> findAll() throws DatabaseException {
List<Customer> dataList = customerDao.findAll();
Iterator<Customer> customerIterator = dataList.iterator();
while(customerIterator.hasNext())
{
Customer customer = customerIterator.next();
System.out.println("Found customer - id:"+customer.getId()+" - username: "+customer.getUsername());
}
return dataList;
}
//Getters and Setters omitted
}
FormRequestController.java
package my.webapp.web.controller;
import java.util.Iterator;
import java.util.List;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import my.webapp.interfaces.DatabaseException;
import my.webapp.web.utilities.NavigationConstants;
/**
* This is a super class for all controllers and all the controllers should
* extend this class. FormRequestController class is responsible for providing
* methods to load UI and methods to handle events produced by HTML components
* like on click of a button or a link.
*
*
*/
public abstract class FormRequestController implements NavigationConstants {
/**
* Error message to be displayed in the UI. If required, this is message
* will be displayed after querying the database for list of any data
* object. These Strings are designed to be used only by the Form Request
* Controller. Hence it is defined here
*/
protected final String NO_SERVICEPROVIDERS_AVAILABLE = "There are no service providers available. Please add a new service provider";
protected final String NO_CUSTOMERS_AVAILABLE = "There are no customers available. Please add a new customer";
/**
* The following is the enumeration type to know the action to be performed
* to process the request. This enumeration is designed to be used in the
* process request method. This variable is specifically for form controller
*
*
*/
protected enum FormActionToPerform {
// To display UI to add a new entity object
SHOW_ADD_VIEW,
// To display UI to modify an existing entity object
SHOW_EDIT_VIEW,
// To display UI to delete an existing entity object
SHOW_DELETE_VIEW,
// To display UI to view the details of the existing object
SHOW_VIEW_TO_VIEW_SELECTED_OBJECT,
// To display UI to view the list of the existing objects either in a
// table or in a list
SHOW_VIEW_FOR_LIST;
}
protected FacesContext context;
/**
* This variable holds the list containing all the existing service
* providers from the database to be represented in the form of a table for
* the user to view/edit/delete. This variable should have a getter and
* setter method so that it could be accessed in the UI
*/
protected List<?> entityObjectList;
private UIComponent component;
/**
* This variable is needed as the UI does not allow the user to edit certain
* properties. A boolean is required in order to find out if the property
* can be modified. The FormActionToPerform cannot be used as most of the
* controllers are request scoped and are also called for adding events to
* menu
*/
protected boolean editMode;
/**
* This variable is used to hold the form name of the JSF form in which data
* is handled so that error messages can be set only for that particular
* form
*/
protected String componentId = null;
/*
* -------------------------------------------------------------------------
* The following methods starting with "showView" are the methods that would
* be used to display forms or any details in the UI
* -------------------------------------------------------------------------
*/
/**
* This method will be called from the UI on click of "menu link". This
* method should load the data from the database (with respect to the
* selection) in the form of HTML Data Table or List. Each row of data can
* contain view, edit and delete buttons to allow the user to work on that
* data
* @throws DatabaseException
*
*/
public String showViewDataTable() throws DatabaseException {
processRequest(FormActionToPerform.SHOW_VIEW_FOR_LIST);
return doShowUIView(FormActionToPerform.SHOW_VIEW_FOR_LIST);
}
/**
* This method will be called from the UI on click of "add new" link/button.
* This method should create a new model object whose properties will be set
* in the form. This method will then return the String of the XHTML page
* that contains the form. The server then loads the form.
*
* @return String URL - If the data object is created successfully return
* form page else return error page
* @throws DatabaseException
*/
public String showViewToAdd() throws DatabaseException {
processRequest(FormActionToPerform.SHOW_ADD_VIEW);
return doShowUIView(FormActionToPerform.SHOW_ADD_VIEW);
}
/**
* This method will be called from the UI on click of "edit" button, usually
* from the data table. This method should get the selected object whose
* properties will be updated in the form.
*
* @return String URL - XHTML page that contains the form with values to be
* edited. or error URL for a different UI or null to redisplay the
* form on failure
* @throws DatabaseException
*
*/
public String showViewToEdit() throws DatabaseException {
processRequest(FormActionToPerform.SHOW_EDIT_VIEW);
return doShowUIView(FormActionToPerform.SHOW_EDIT_VIEW);
}
/**
* This method will be called from the UI on click of "delete" button,
* usually from the HTML data table. This method should get the selected
* object which will be deleted/or made inactive in the database.
*
* @return String URL - If the data object is deleted successfully return
* success page else return error page
* @throws DatabaseException
*
*/
public String showViewToDeleteDetails() throws DatabaseException {
processRequest(FormActionToPerform.SHOW_DELETE_VIEW);
return doShowUIView(FormActionToPerform.SHOW_DELETE_VIEW);
}
/**
* This method will be called from the UI on click of "view" button, usually
* from the data table. This method should get the selected object whose
* properties will be viewed in the form.This method will return XHTML URL
* as a string that will contain the form with values to be viewed. The
* server then loads the form.The user will not be allowed to edit in this
* form.
*
* @return String URL - If the data object is deleted successfully return
* success page else return error page
* @throws DatabaseException
*
*/
public String showViewToViewDetails() throws DatabaseException {
processRequest(FormActionToPerform.SHOW_VIEW_TO_VIEW_SELECTED_OBJECT);
return doShowUIView(FormActionToPerform.SHOW_VIEW_TO_VIEW_SELECTED_OBJECT);
}
/**
* This method should --not--- be called by the UI or by the sub class but
* by the super class. This method is called usually to display a new form,
* a form with edit to modify, a UI to view details
*
* @return
*/
abstract String doShowUIView(FormActionToPerform action);
/**
* This method is usually called by the super class to process a request.
* All necessary steps to process a request to create a new object for
* adding new data, to get the selected object to edit/view/delete
* @throws DatabaseException
*/
abstract void processRequest(FormActionToPerform action) throws DatabaseException;
/**
* This method is usually called by the UI to bind the list of data in a
* data table or a form or to bind value for a hidden value. All necessary
* steps to bind data like setting the value should be done here
*/
protected void bindData() {
}
/*
* ----------------------------------------------------------------------
* The following are methods that classes should call to save or update the
* database through their service classes
* ----------------------------------------------------------------------
*/
/**
* This method is called from the UI on click of the save button in the
* form. This method should be called when a new data has to be saved/or an
* existing data was modified and has to be updated in the DB This method
* should in turn call the respective service method to save form data in
* the database.
*
*
* @return String - success URL on successful update in the DB An error URL
* for a different UI or null to redisplay the form is returned on
* failure
*/
abstract String save();
/**
* This method is called from the UI on click of the delete button/ or a
* confirmation button, usually from the data table. This method should get
* the selected object whose data should be removed from the database. This
* method should in turn call the respective service method to remove data.
*
* @return An error URL for a different UI or null to redisplay the form is
* returned by default. A success URL be returned on successful
* operation
*/
abstract String delete();
/*
* ----------------------------------------------------------------------
* The following methods starting with "find" are the methods the classes
* should call to get data from the database through their service classes.
* These method should be called to get list of data objects to be displayed
* in the data table or to get a specific data object to be edited or
* deleted. The methods are named starting with "find" rather than get, just
* to avoid confusions with the bean get methods. All data objects extend
* class Configuration Data
* ----------------------------------------------------------------------
*/
/**
* This method is called to get a list of all data from the data base. For
* example, the list will be a list of all customers or service providers.
* This method will in turn call the findAll method of the services class
* which will interact with the database
*
* @return List<ConfigurationData>
* @throws DatabaseException
*/
protected List<?> findAll() throws DatabaseException {
return null;
}
/**
* This method is called to get a list of all data from the data base. For
* example, the list will be a list of all customers or service providers
* but will be of type Select Item. This method is called when the UI
* requires a single select combo box or a multiple select list box
*
*
* @return List<SelectItem>
*/
protected List<SelectItem> findAllSelectItems() {
return null;
}
/**
* This method is responsible to add error message to the current faces
* context. The error message that is set here applies to the whole view or
* UI and not specific to any fields. Any field specific validation messages
* should be handled in the UI.
*
* @param message
*/
public void setErrorMessage(String message) {
String componentId = null;
if (null != component) {
componentId = component.getClientId();
}
if (null != componentId) {
this.getContext().addMessage(componentId,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "", message));
}
}
public void resetErrorMessages() {
Iterator<FacesMessage> errorMessages = this.getContext().getMessages();
while (errorMessages.hasNext()) {
errorMessages.next();
errorMessages.remove();
}
}
//getters and setters omitted
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>MyWebApp</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<context-param>
<description>State saving method: 'client' or 'server' (=default). See JSF Specification 2.5.2</description>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
<param-value>resources.application</param-value>
</context-param>
<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/balusc.taglib.xml</param-value>
</context-param>
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
<filter>
<filter-name>PrimeFaces FileUpload Filter</filter-name>
<filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>PrimeFaces FileUpload Filter</filter-name>
<servlet-name>Faces Servlet</servlet-name>
</filter-mapping>
<context-param>
<param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
<param-value>false</param-value>
</context-param>
<listener>
<listener-class>com.sun.faces.config.ConfigureListener</listener-class>
</listener>
</web-app>
faces-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
version="2.0">
</faces-config>
Upvotes: 1
Views: 1909
Reputation: 1595
I think the problem is the javax.faces.bean.RequestScoped
annotation. This one is only recognized by JSF but your bean seems to be managed by CDI. Without a specific scope annotation CDI beans are in dependent scope. As far as I know, if a bean is in dependent scope, a new instance is created for every EL expression in a JSF page. Do you have 16 EL expressions where the bean is used in your page?
The solution should be simple: use javax.enterprise.context.RequestScoped
instead.
Upvotes: 2