Referencing CDI producer method result in h:selectOneMenu
I have a named session scoped bean CustomerRegistration
which has a named producer method getNewCustomer
which returns a Customer
object. There is also CustomerListProducer
class which produces all customers as list from the database. On the selectCustomer.xhtml page the user is then able to select one of the customers and submit the selection to the application which then simply prints out the last name of the selected customer.
Now this only works when I reference the selected customer on the facelets page via #{customerRegistration.newCustomer}
. When I simply use #{newCustomer}
then the output for the last name is null
whenever I submit the form.
What's going on here? Is this the expected behavior as according to chapter 7.1 Restriction upon bean instantion of JSR-299 spec?
It says:
... However, if the application directly instantiates a bean class, instead of letting the container perform instantiation, the resulting instance is not managed by the container and is not a contextual instance as defined by Section 6.5.2, “Contextual instance of a bean”. Furthermore, the capabilities listed in Section 2.1, “Functionality provided by the container to the bean” will not be available to that particular instance. In a deployed application, it is the container that is responsible for instantiating beans and initializing their dependencies. ...
Here's the code:
Customer.java:
@javax.persistence.Entity
@Veto
public class Customer implements Serializable, Entity {
private static final long serialVersionUID = 122193054725297662L;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Id
@GeneratedValue()
private Long id;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return firstName + ", " + lastName;
}
@Override
public Long getId() {
return this.id;
}
}
CustomerListProducer.java:
@SessionScoped
public class CustomerListProducer implements Serializable {
@Inject
private EntityManager em;
private List<Customer> customers;
@Inject
@Category("helloworld_as7")
Logger log;
// @Named provides access the return value via the EL variable name
// "members" in the UI (e.g.,
// Facelets or JSP view)
@Produces
@Named
public List<Customer> getCustomers() {
return customers;
}
public void onCustomerListChanged(
@Observes(notifyObserver = Reception.IF_EXISTS) final Customer customer) {
// retrieveAllCustomersOrderedByName();
log.info(customer.toString());
}
@PostConstruct
public void retrieveAllCustomersOrderedByName() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> criteria = cb.createQuery(Customer.class);
Root<Customer> customer = criteria.from(Customer.class);
// Swap criteria statements if you would like to try out type-safe
// criteria queries, a new
// feature in JPA 2.0
// criteria.select(member).orderBy(cb.asc(member.get(Member_.name)));
criteria.select(customer).orderBy(cb.asc(customer.get("lastName")));
customers = em.createQuery(criteria).getResultList();
}
}
CustomerRegistration.java:
@Named
@SessionScoped
public class CustomerRegistration implements Serializable {
@Inject
@Category("helloworld_as7")
private Logger log;
private Customer newCustomer;
@Produces
@Named
public Customer getNewCustomer() {
return newCustomer;
}
public void selected() {
log.info("Customer " + newCustomer.getLastName() + " ausgewählt.");
}
@PostConstruct
public void initNewCustomer() {
newCustomer = new Customer();
}
public void setNewCustomer(Customer newCustomer) {
this.newCustomer = newCustomer;
}
}
not working selectCustomer.xhtml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Auswahl</title>
</h:head>
<h:body>
<h:form>
<h:selectOneMenu value="#{newCustomer}" converter="customerConverter">
<f:selectItems value="#{customers}" var="current"
itemLabel="#{current.firstName}, #{current.lastName}" />
</h:selectOneMenu>
<h:panelGroup id="auswahl">
<h:outputText value="#{newCustomer.lastName}" />
</h:panelGroup>
<h:commandButton value="Klick"
action="#{customerRegistration.selected}" />
</h:form>
<开发者_JAVA技巧/h:body>
</html>
working selectCustomer.xhtml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Auswahl</title>
</h:head>
<h:body>
<h:form>
<h:selectOneMenu value="#{customerRegistration.newCustomer}" converter="customerConverter">
<f:selectItems value="#{customers}" var="current"
itemLabel="#{current.firstName}, #{current.lastName}" />
</h:selectOneMenu>
<h:panelGroup id="auswahl">
<h:outputText value="#{newCustomer.lastName}" />
</h:panelGroup>
<h:commandButton value="Klick"
action="#{customerRegistration.selected}" />
</h:form>
</h:body>
</html>
CustomerConverter.java:
@SessionScoped
@FacesConverter("customerConverter")
public class CustomerConverter implements Converter, Serializable {
private static final long serialVersionUID = -6093400626095413322L;
@Inject
EntityManager entityManager;
@Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
Long id = Long.valueOf(value);
return entityManager.find(Customer.class, id);
}
@Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
return ((Customer) value).getId().toString();
}
}
Think of how @Producer
s are registered. During deployment time, the container scans your classes for annotations and the fact that you have your @Producer
method declared inside a @SessionScoped @Named
bean, doesn't mean that you will have as many producers as instances of that bean, nor does it mean that the container will call the producer from the instance you expect it to be called.
What happens here is - your producer method always returns the same instance of Customer
, the one that was created in your @PostConstruct
method during deployment, i.e. during @Producer
registration.
This is the expected behavior.
It seems that you want something that will give you a new Customer
entity per session. In that case, the correct way to do this would be:
public class CustomerProducer {
@Produces @Named @SessionScoped
public Customer getNewCustomer(@New Customer customer) {
// do some custom init if need be
return customer;
}
}
Then remove @Producer
related annotations from you session scoped bean. Now you can use `#{newCustomer}, which will always give you a new container managed instance per session.
精彩评论