每次导航到某个页面或执行操作时,我的CustomerController都会被多次注入。我的CustomerController是Request Scoped,所以我理解每个请求都意味着创建并注入了一个新实例,但我看到的内容对我来说似乎过分了。
例如,在部署我的应用程序(没有错误)后,我导航到index.xhtml
页面。在index.xhtml
页面上,点击“添加客户”链接,该链接将我带到customer.xhtml
页面。这导致customer.xhtml
页面加载时有16个CustomerController注入!这条线
CustomerController.init调用
(和相关的输出)出现16次。由于init
方法使用@PostConstruct
注释,因此在注入CustomerController后调用它。
当我尝试保存新客户时,我从我输入的各种println
调用中添加了1 500行添加到我的日志中以帮助调试。然而,大多数都是重复,因为控制器被注射了很多次。
为什么CustomerController注入了这么多次?
我注意到的是,如果我导入javax.enterprise.context.RequestScoped
类而不是javax.faces.bean.RequestScoped
类,那么我会得到我期望的单次注入行为。我想这意味着问题在于JSF和(更有可能)我使用JSF而不是我一般注入bean的方式。
的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>
答案 0 :(得分:2)
我认为问题是javax.faces.bean.RequestScoped
注释。这个只能被JSF识别,但你的bean似乎是由CDI管理的。没有特定范围的注释CDI bean在依赖范围内。据我所知,如果bean在依赖范围内,则为JSF页面中的每个EL表达式创建一个新实例。你有16个EL表达式在你的页面中使用bean吗?
解决方案应该很简单:改为使用javax.enterprise.context.RequestScoped
。