Mit JavaServer Faces können bequem und leicht Formulare verwaltet werden. Als Beispiel soll ein Formular zur Registrierung eines Benutzers dienen. Damit die Eingaben in einem Formular serverseitig ausgewertet werden können, braucht jedes JSF-Formular neben seiner XHTML-Ansicht auch eine Instanz, an welche die Eingabedaten gekoppelt werden können. In Java EE 5 wurde eine solche Instanz als Backing Bean bezeichnet. Mit Java EE 6 hat man die Backing Bean dann als eine Variante der Managed Bean untergeordnet und mit Java EE 7 wird in Zukunft alles unter den Begriff Web Beans bzw. CDI fallen. Bevor wir uns aber mit CDI (Contexts and Dependency Injection) auseinandersetzen, möchte ich kurz zeigen, wie das Zusammenspiel aus Managed Bean und JSF-Formular aussieht.
Formulareingaben mit JSF verarbeiten
XHTML
<h:form> <label for="user-name">Name</label> <h:inputText id="user-name" value="#{userBackingBean.name}" p:placeholder="John Doe" /> <label for="user-email">Email</label> <h:inputText id="user-email" value="#{userBackingBean.email}" p:placeholder="Email" /> <label for="user-password">Password</label> <h:inputText id="user-password" value="#{userBackingBean.password}" p:placeholder="Password" /> <h:commandButton value="Register" action="#{userBackingBean.submit}" /> </h:form> |
UserBackingBean.java
package com.welovecoding.web.registration; import java.io.Serializable; import java.util.logging.Level; import java.util.logging.Logger; import javax.enterprise.context.SessionScoped; import javax.faces.bean.ManagedBean; @ManagedBean @SessionScoped public class UserBackingBean implements Serializable { private final static Logger LOGGER = Logger.getLogger(UserBackingBean.class.getSimpleName()); private String name; private String email; private String password; public UserBackingBean() { } public void submit() { String template = "Name: {0}, Email: {1}, Password: {2}"; Object[] values = new Object[]{ this.name, this.email, this.password }; LOGGER.log(Level.INFO, template, values); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } |
Der hier gezeigt Code sorgt dafür, dass nachdem Absenden des Formulars alle eingegebenen Daten in der Konsole des ausführenden Anwendungsservers (z.B. GlassFish 4) geloggt werden.
Deprecated Managed Beans
Ab Java EE Version 7 warnt der Compiler für dem Einsatz der Managed Bean-Annotation mit der Meldung: „Annotations from the package javax.faces.bean will be deprecated in the next JSF version. CDI ones are recommended instead“. In diesem Fall muss man als Programmierer nur die @ManagedBean
-Deklaration gegen @Named
aus dem Paket javax.inject
ersetzen.
Mit JSF versendete Formulardaten in einer Datenbank speichern
Zur Speicherung der Daten, die über ein JSF-Formular gesendet wurden, werden vier Akteure benötigt:
- Das
JSF-Formular
zur Eingabe der Daten - Eine
Backing Bean
zur Bindung der Formulardaten - Eine
Entity
, welche die empfangenen Daten auf einer Datenbank abbildet - Ein
Repository
, welches über einePersistence Unit
die Operationen auf der Datenbank ausführt
Der Verarbeitungsablauf sieht dann wie folgt aus: Das JSF-Formular steht mit der Backing Bean in Verbindung und überliefert alle Eingabedaten an die Backing Bean. Die Backing Bean wiederum validiert die Eingabedaten (im Beispiel mit der Methode validate
) und erstellt bei einer gültigen Eingabe eine Instanz der Klasse UserEntity
, welche unsere Geschäftsdaten für eine Benutzerregistration auf unserer Datenbank abbildet. Die UserEntity
wird dann über das UserRepository
in der Datenbank gespeichert. Nach erfolgreicher Speicherung gibt dann die UserBackingBean
in der Methode submit
eine JSF-Zeichenkette zurück, welche einen Webseiten-Redirect auslöst.
Weil das UserRepository
über die Annotation @Stateless
zu einer Enterprise JavaBean (EJB) geworden ist, kann es über die Annotation @EJB
in der UserBackingBean
verwendet werden.
UserBackingBean.java
package com.welovecoding.web.registration; import java.io.Serializable; import java.util.logging.Level; import java.util.logging.Logger; import javax.ejb.EJB; import javax.enterprise.context.SessionScoped; import javax.inject.Named; @Named @SessionScoped public class UserBackingBean implements Serializable { @EJB private UserRepository userRepository; private final static Logger LOGGER = Logger.getLogger(UserBackingBean.class.getSimpleName()); private String name; private String email; private String password; public UserBackingBean() { } public String submit() { String redirect = "/error?faces-redirect=true"; String template = "Name: {0}, Email: {1}, Password: {2}"; Object[] values = new Object[]{ this.name, this.email, this.password }; LOGGER.log(Level.INFO, template, values); boolean isValid = validate(); if (isValid) { UserEntity entity = new UserEntity(name, email, password); userRepository.save(entity); LOGGER.log(Level.INFO, "Saved user."); redirect = "/success?faces-redirect=true"; } return redirect; } public boolean validate() { return name.length() > 1 && email.length() > 1 && password.length() > 1; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } |
UserEntity.java
package com.welovecoding.web.registration; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @Entity @Table(name = "users") public class UserEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @NotNull @Size(min = 1, max = 255) @Column private String name; @NotNull @Column private String email; @NotNull @Column private String password; public UserEntity() { } public UserEntity(String name, String email, String password) { this.name = name; this.email = email; this.password = password; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } |
UserRepository.java
package com.welovecoding.web.registration; import com.welovecoding.web.config.Names; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Stateless public class UserRepository { @PersistenceContext(unitName = "your-Persistence-Unit") EntityManager em; public UserRepository() { } public void save(UserEntity user) { em.persist(user); em.flush(); } } |
Hinweis: Sollte die Datenbank-Tabelle „users“ der UserEntity
noch nicht vorhanden sein, so kann sie automatisch bei einem Aufruf erstellt werden. Dazu muss man nur die „Table Generation Strategy“ der Webapplikation ändern. Das tut man in der Datei „webapp/src/main/resources/META-INF/persistence.xml“ mit einem Eintrag für javax.persistence.schema-generation.database.action
.
Beispiel:
persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <!-- Persistence Unit for MySQL --> <persistence-unit name="com.welovecoding.web_wlc-webapp_war_1.0-SNAPSHOTPU" transaction-type="JTA"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <jta-data-source>jdbc/welovecoding</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="javax.persistence.schema-generation.database.action" value="create"/> </properties> </persistence-unit> </persistence> |
Eingabefehler anzeigen
Um eine Meldung bei einer fehlerhaften Eingabe anzuzeigen, muss ein h:message
in der XHTML-Seite eingefügt werden. Das h:message
referenziert dann über sein for
-Attribut die id
des entsprechenden Eingabefelds. Sofern ein h:message
-Feld vorliegt, kann dann über die Backing Bean
in das Feld über die Angabe der clientId
geschrieben werden. Die clientId
setzt sich zusammen aus der ID des Formulars und der ID des Eingabefelds verbunden durch einen Doppelpunkt. Beispiel: formId:inputId
. Ein Code-Beispiel soll diese Zuordnung verdeutlichen:
JSF-Formular
<h:form id="register-user-form"> <label for="user-name">Name</label> <h:inputText id="user-name" value="#{userBackingBean.name}" p:placeholder="John Doe" /> <h:message for="user-name" /> <label for="user-email">Email</label> <h:inputText id="user-email" value="#{userBackingBean.email}" p:placeholder="Email" /> <h:message for="user-email" /> <label for="user-password">Password</label> <h:inputText id="user-password" value="#{userBackingBean.password}" p:placeholder="Password" /> <h:message for="user-password" /> <h:commandButton value="Register" action="#{userBackingBean.submit}" /> </h:form> |
UserBackingBean.java
... @Named @SessionScoped public class UserBackingBean implements Serializable { @EJB private UserRepository userRepository; private final static Logger LOGGER = Logger.getLogger(UserBackingBean.class.getSimpleName()); private String name; private String email; private String password; public UserBackingBean() { } public String submit() { String formId = "register-user-form"; String inputId = "user-name"; String clientId = formId + ":" + inputId; String message = "Error: Your username is too short."; FacesContext.getCurrentInstance().addMessage(clientId, new FacesMessage(message)); return ""; } ... } |
Hinweis: Die Rückgabe einer leeren Zeichenkette („“) führt in JSF dazu, dass wieder auf Seite weitergeleitet wird, von der das Formular abgeschickt wurde.
Eigene Validierungsmethoden pro Eingabefeld implementieren
Es ist möglich, jedem Eingabefeld eine eigene Validerungsmethode über das validator
-Attribut zuzuweisen (siehe JavaServer Faces JSF Validation Tutorial). Wie das geht, zeigt folgender exemplarischer Code:
JSF-Formular
<h:form id="register-user-form"> <label for="user-name">Name</label> <h:inputText id="user-name" value="#{userBackingBean.name}" validator="#{userBackingBean.checkName}" p:placeholder="John Doe" /> <h:message for="user-name" /> <h:commandButton value="Register" action="#{userBackingBean.submit}" /> </h:form> |
UserBackingBean.java
... @Named @SessionScoped public class UserBackingBean implements Serializable { ... public UserBackingBean() { } public String submit() { return ""; } public void checkName(FacesContext context, UIComponent component, Object value) { String formId = "register-user-form"; String inputId = "user-name"; String clientId = formId + ":" + inputId; String message = "Error: Your username is too short."; FacesContext.getCurrentInstance().addMessage(clientId, new FacesMessage(message)); } ... } |
Hinweis: Die clientId
kann auch über die UIComponent
bezogen werden. Beispiel:
public void checkName(FacesContext context, UIComponent component, Object value) { String message = "Error: Your username is too short."; FacesContext.getCurrentInstance().addMessage(component.getClientId(), new FacesMessage(message)); } |
Validierung mit AJAX
Möchte man die checkName
-Methode aus dem vorherigen Beispiel nicht erst bei einem Klick auf den h:commandButton
machen, sondern sofort, nachdem der Nutzer das Feld ausgefüllt hat, so kann man in JSF 2.2 Gebrauch von AJAX-Requests machen (siehe Ajax Validation).
JSF-Formular
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://xmlns.jcp.org/jsf/passthrough" xmlns:f="http://xmlns.jcp.org/jsf/core"> ... <h:form id="register-user-form"> <label for="user-name">Name</label> <h:inputText id="user-name" value="#{userBackingBean.name}" validator="#{userBackingBean.checkName}" p:placeholder="John Doe"> <f:ajax event="blur" render="register-user-name-message" /> <h:message for="user-name" id="register-user-name-message" /> <h:commandButton value="Register" action="#{userBackingBean.submit}"> <f:ajax execute="@form" render="@form" /> </h:commandButton> </h:form> ... |
Hinweis: Bei Verwendung von AJAX-Validierungen sollte die Backing Bean die Annotation @ViewScoped
(Package: javax.faces.view
) haben und nicht @SessionScoped
, da sonst bei jedem AJAX-Request eine neue Bean erzeugt wird.
Wer noch die extra Meile gehen möchte, der kann dank der mit JSF 2.2 eingeführten Pass-through Elements die JSF-Tags h:form
, h:inputText
& Co. durch Standard-HTML5-Elemente ersetzen. Dazu ist aber die Verwendung des Namespaces xmlns:jsf="http://xmlns.jcp.org/jsf"
nötig.
TODO:
– Validierung der Eingabedaten mit eigener Annotation