#jsf-2 #cdi #postconstruct
#jsf-2 #cdi #постконструкция
Вопрос:
Каждый раз, когда я перехожу на страницу или выполняю действие, мой CustomerController вводится несколько раз. Мой CustomerController ограничен областью запроса, поэтому я понимаю, что каждый запрос будет означать, что создается и вводится новый экземпляр, но то, что я вижу, кажется мне чрезмерным.
Например, после развертывания моего приложения (без ошибок) Я перехожу на index.xhtml
страницу. Со index.xhtml
страницы я нажимаю на ссылку «Добавить клиентов», которая ведет меня на customer.xhtml
страницу. Это приводит к 16 инъекциям CustomerController к моменту customer.xhtml
загрузки страницы! Строка
Вызывается CustomerController.init
(и связанный с ним вывод) появляется 16 раз. Поскольку init
метод аннотируется @PostConstruct
, он вызывается после ввода CustomerController.
Когда я пытаюсь сохранить нового клиента, я получаю 1 500 строк, добавленных в мой журнал из различных println
вызовов, которые я ввожу для помощи в отладке. Однако большинство из них являются повторениями, потому что контроллер вводится так много раз.
Почему CustomerController вводится так много раз?
Что я заметил, так это то, что если я импортирую javax.enterprise.context.RequestScoped
класс вместо javax.faces.bean.RequestScoped
класса, то я получаю поведение одиночной инъекции, которое я ожидал. Я предполагаю, что это означает, что проблема связана с JSF и (что более вероятно) с моим использованием JSF, а не с тем, как я вводил компоненты в целом.
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>
Ответ №1:
Я думаю, что проблема в javax.faces.bean.RequestScoped
аннотации. Этот распознается только JSF, но ваш компонент, похоже, управляется CDI. Без аннотации конкретной области компоненты CDI находятся в зависимой области. Насколько я знаю, если компонент находится в зависимой области видимости, для каждого выражения EL на странице JSF создается новый экземпляр. Есть ли у вас 16 EL-выражений, в которых компонент используется на вашей странице?
Решение должно быть простым: используйте javax.enterprise.context.RequestScoped
вместо этого.
Комментарии:
1. У меня была такая же идея (по одному экземпляру на выражение EL), но по моим подсчетам таких выражений не 16. Я думал, что, поскольку я использую
javax.faces.bean.RequestScoped
аннотацию, она будет управляться JSF. Что мне следует изменить, чтобы компонент управлялся JSF (т. Е. Не могли бы вы уточнить)? Использованиеjavax.enterprise.context.RequestScoped
работает, но я хочу знать, почему аннотация JSF не работает и как заставить ее работать.2. Вам просто нужно заменить
@Named
на@javax.faces.bean.ManagedBean
, чтобы компонент управлялся JSF. В этом случае компонент по-прежнему также является компонентом CDI, но с удалением@Named
компонента CDI больше не доступен через EL, а компонент JSF разрешается в выражениях EL (компоненты CDI с тем же именем всегда разрешаются первыми). Но если у вас нет веских причин использовать компоненты JSF, CDI в любом случае может быть лучшим выбором.3. Этот ответ не является окончательным ответом, но пока он лучший, и переход с JSF ManagedBeans на CDI сработал.