JavaServer Faces (JSF)
dr hab. inż. Marek Wojciechowski
JavaServer Faces (JSF)
• Technologia ułatwiająca i standaryzująca tworzenie
interfejsu użytkownika w webowych aplikacjach Java EE
• Część specyfikacji Java EE (od J2EE 1.4)
• Oparta o stanowe, konfigurowalne komponenty interfejsu użytkownika pracujące po stronie serwera
• Koncepcja podobna do Web Forms w ASP.NET
• Wprowadzona jako framework dla serwletów i JSP
– Obecnie pozycjonowana jako technologia w związku z uniezależnieniem się od JSP
• Główne elementy technologii JSF:
– API do reprezentowania komponentów interfejsu użytkownika i zarządzania nimi
– Biblioteki znaczników dla JSP/Facelets
JSF jako framework
• Przypisanie ról technologiom składowym:
– serwlety vs. JSP
• Zakres funkcjonalny:
– stanowy, komponentowy interfejs użytkownika – obsługa nawigacji między stronami
– walidacja danych
– wsparcie dla aplikacji wielojęzycznych
• JSF jako implementacja wzorca architektonicznego MVC:
– model komponentów do tworzenia stron-widoków
– gotowy, konfigurowalny kontroler: FacesServlet
– współpracuje z różnymi modelami
JSF: Interfejs użytkownika po stronie serwera
• Strona (JSP/Facelets) odwołuje się do komponentów
interfejsu zaimplementowanych w JSF poprzez znaczniki
• Interfejs użytkownika zarządza komponentami
i związanymi z nimi walidatorami, konwerterami itp.
• Interfejs użytkownika pracuje po stronie serwera i jest
„renderowany” jako HTML wysyłany klientowi
Serwer aplikacji
*.xhtml
JSF UI
Żądanie HTTP
Odpowiedź HTTP
HTML
Podstawowe kroki tworzenia aplikacji JSF
• Konfiguracja serwletu kontrolera jako punktu wejścia do aplikacji
• Utworzenie stron-widoków z wykorzystaniem znaczników odwołujących się do komponentów JSF
• Zdefiniowanie nawigacji między stronami
– Znaczniki, kod Java, opcjonalnie XML
• Implementacja i konfiguracja (adnotacje / XML)
komponentów JavaBean do wiązania danych i obsługi zdarzeń ze stron (tzw. backing beans)
• Internacjonalizacja, zabezpieczenie, style (skórki), …
Kontroler JSF: FacesServlet
• Serwlet FacesServlet pełni funkcję kontrolera / routingu (wzorzec „front controller”)
• Stanowi pojedynczy punkt wejścia do aplikacji (odwzorowanie w web.xml)
• Konfiguracja poprzez plik faces-config.xml (opcjonalny w JSF 2.0)
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
web.xml
Struktura stron JSF
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<!-- HTML tags -->
<f:view>
<!-- HTML and JSF tags -->
</f:view>
<!-- HTML tags -->
*.jsp (JSP)
<?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:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<!-- HTML and JSF tags -->
</html>
*.xhtml (Facelets)
Facelets
• Język definicji widoków opracowany z myślą o JSF
– brak problemu niezgodności cykli życia JSP/JSF
• Oparty o XHTML
• Oferuje własne biblioteki znaczników, ale obsługuje również:
– znaczniki JSF (Core i HTML)
– znaczniki JSTL (Core i Functions)
• Obsługuje Unified EL
• Strony nie są kompilowane do Javy!
– komponenty JSF reprezentowane jako drzewo komponentów – kod XML strony parsowany parserem SAX parser i cache’owany
w pamięci
• Domyślny format stron JSF od JSF 2.0
Zalety Facelets
• Unika problemów niezgodności cykli życia JSP/JSF
• Wspiera reużywalność kodu
– szablony
– komponenty złożone
• Szybka kompilacja
• Walidacja wyrażeń EL w czasie kompilacji
• Wydajne renderowanie strony
Przykładowa aplikacja JSF
Formularz logowania
<?xml version='1.0' encoding='UTF-8' ?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>...<title>Logowanie</title></h:head>
<h:body><h:form>
<p>Username: <h:inputText value="#{loginBean.username}"
id="username"/>
<p>Password: <h:inputSecret value="#{loginBean.password}"
id="password"/>
<p><h:commandButton value="Log in"
id="submitButton"
action="#{loginBean.login()}"/>
</h:form></h:body>
</html>
index.xhtml
Nawigacja statyczna
<h:commandButton value="Log in"
id="submitButton"
action="success"/>
index.xhtml
<navigation-rule>
<from-view-id>/login.jsp</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/ok.jsp</to-view-id>
</navigation-case>
</navigation-rule>
faces-config.xml (opcjonalny)
• Odwzorowanie w XML nie jest konieczne jeśli nazwa strony
jest taka jak nazwa akcji (implicit vs. explicit navigation)
Mechanizm nawigacji w JSF
• Przyciski commandButton i linki commandLink powodują zatwierdzenie formularza, w którym się znajdują
• Formularz JSF zawsze wywołuje sam siebie
– Żądanie przechodzi przez kontroler
– Kontroler może dokonać programowej nawigacji do innego widoku
• Formularze domyślnie zatwierdzane metodą POST
• Nawigacja do nowego widoku realizowana przez kontroler domyślnie jako „forward” (przekierowanie po stronie
serwera bez udziału przeglądarki)
– Adres URL w przeglądarce wskazuje na poprzednią stronę – Problematyczne tworzenie zakładek
• Można jawnie zlecić „redirect” przy nawigacji
– action="success?faces-redirect=true"
• Od JSF 2.0 obsługa żądań GET i parametrów widoku
– Dla wywołań JSF spoza aplikacji JSF
Implicit vs. explicit navigation
• Nawigacja niejawna (action name = page file name)
– prostota (convention over configuration) – mniej XML-a w aplikacji
• Nawigacja jawna (reguły w faces-config.xml)
– elastyczność
– wildcards, globalne reguły nawigacji
– informacje o nawigacji przechowywane poza stronami i klasami
– edytory wizualne w IDE
Komponenty backing beans
• Komponenty backing beans definiują właściwości i metody powiązane z komponentami interfejsu użytkownika
– każda właściwość powiązania z wartością lub instancją komponentu – dodatkowo backing bean może zawierać metody:
• do walidacji danych komponentu
• do obsługi zdarzeń generowanych przez komponent
• do przetwarzania związanego z nawigacją
• Ich klasy muszą spełniać reguły Java Beans
• Można je zakwalifikować jako implementację wzorca
„view helper”, ale z racji obsługi akcji nawigacyjnych określane są też niekiedy jako kontrolery
(wzorzec „application controller”)
Koncepcja managed beans
• Komponenty Java Beans, których cyklem życia i zasięgiem zarządza środowisko uruchomieniowe
• Obecnie konfigurowane adnotacjami w kodzie klasy
– Dawniej w XML (faces-config.xml)
• Obecnie bazujące na technologii CDI
– Dawniej dedykowane komponenty w ramach JSF
• Zależnie od roli w aplikacji określane jako:
– Backing bean (gdy powiązany z konkretną stroną i ją wspierający) – Application logic bean
• Odwołania do managed beans z poziomu kodu strony JSF realizowane są za pomocą języka wyrażeń Unified EL
– Managed beans mogą również korzystać z innych managed beans
poprzez wstrzykiwanie zależności (ang. dependency injection)
Zasięgi beanów CDI używane w JSF
• Standardowe zasięgi CDI
– @RequestScoped (obsługa danych i zdarzeń z formularza) – @SessionScoped (przechowanie danych na czas sesji HTTP)
– @ApplicationScoped (wspólna instancja dla wszystkich użytkowników) – Uwaga: Domyślny @Dependent nieodpowiedni dla beanów
wywoływanych bezpośrednio ze strony
• Specyficzne zasięgi dla CDI w JSF:
– @ViewScoped (przechowanie danych dopóki użytkownik na stronie) – @FlowScoped (zasięg przepływu w ramach modularnej nawigacji)
• Obiekt flash
– nie jest to zasięg, tylko programowo dostępna mapa
– przechowuje dane na czas pojedynczej operacji redirect
Unified Expression Language
• Połączenie języka wyrażeń (EL) JSP z językiem wyrażeń opracowanym dla pierwszych wersji JSF
• JSP wykorzystuje natychmiastowe wartościowanie wyrażeń
– notacja ${...}
– tylko odczyt danych (wyrażenia read-only) – dostęp do właściwości obiektów
• JSF wykorzystuje odroczone wartościowanie wyrażeń
– notacja #{...}
– odczyt i zapis danych
– dostęp do właściwości i metod obiektów
Backing bean (CDI):
Klasa komponentu
package view.backing;
import javax.inject.Named;
import javax.enterprise.context.RequestScoped;
@Named(value = "loginBean")
@RequestScoped public class LoginBean { private String username;
private String password;
public void setUsername(String t) { this.username = t; } public String getUsername() { return username; } public void setPassword(String t) { this.password = t; } public String getPassword() { return password; } } public String login() { … }
}
LoginBean.java
Backing bean (CDI):
Powiązania na stronie
<?xml version='1.0' encoding='UTF-8' ?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>...<title>Logowanie</title></h:head>
<h:body><h:form>
<p>Username: <h:inputText value="#{loginBean.username}"
id="username"/>
<p>Password: <h:inputSecret value="#{loginBean.password}"
id="password"/>
<p><h:commandButton value="Log in"
id="submitButton"
action="#{loginBean.login()}"/>
</h:form></h:body>
</html>
index.xhtml
Sposoby wiązania Backing Beans z komponentami interfejsu
• Właściwość komponentu backing bean może być powiązana z wartością lub instancją komponentu
• Powiązanie właściwości z wartością komponentu:
– np. <h:inputText … value="#{userBean.userName}" />
– zalecane w większości sytuacji
• Powiązanie właściwości z instancją komponentu:
– np. <h:inputText … binding="#{userBean.userNameInput}" />
– wymagane gdy istnieje potrzeba programowego modyfikowania
właściwości komponentu
Nawigacja dynamiczna (1/2)
<h:commandButton value="Log in"
id="submitButton”
action="#{loginBean.login}"/>
index.xhtml
public class LoginBean {
private String username;
private String password;
...
public String login () {
if (username.equals(password)) return "success";
else return "failure";
} }
LoginBean.java
Wynik akcji zwracany przez metodę komponentu backing bean
marek
***
index.xhtml
success
failure
success.xhtml
failure.xhtml
Nawigacja dynamiczna (2/2)
...
<navigation-rule>
<from-view-id>/index.xhtml</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/ok.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>failure</from-outcome>
<to-view-id>/error.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
...
faces-config.xml (optional)
Walidacja danych w JSF
• Walidacja ręczna - w metodzie akcji
• Niejawna walidacja automatyczna
– użycie atrybutu REQUIRED
– właściwości Backing Beans typów prostych – system wyświetla formularz ponownie
– wyświetlanie komunikatów o błędach: <h:message>
• Jawna walidacja automatyczna
– użycie predefiniowanych komponentów walidatorów – system wyświetla formularz ponownie
– wyświetlanie komunikatów o błędach: <h:message>
• Walidatory aplikacji (custom validators)
• Walidacja zgodna ze standardem Bean Validation
w komponentach backing bean
Walidacja w JSF: Przykład 1
• Obowiązkowa nazwa użytkownika
...
<h:inputText
binding="#{loginBean.inputText1}"
id="inputText1" required="true"/>
<h:message for="inputText1" />
...
index.xhtml
Walidacja w JSF: Przykład 2
• Hasło od 4 do 6 znaków
<h:inputSecret
binding="#{loginBean.inputSecret1}"
id="inputSecret1">
<f:validateLength maximum="6" minimum="4"/>
</h:inputSecret>
<h:message for="inputSecret1" />
index.xhtml
Modyfikacja standardowych komunikatów (obowiązkowość, walidacja, konwersja)
• Własny plik komunikatów nadpisujący ustawienia z domyślnego pliku zawartego w bibliotekach JSF
• Od JSF 1.2 możliwe użycie atrybutów komponentów:
– requiredMessage, converterMessage, validatorMessage
javax.faces.component.UIInput.REQUIRED=Value is required.
javax.faces.validator.LengthValidator.MAXIMUM=Value is greater than allowable maximum of ''{0}'' javax.faces.validator.LengthValidator.MINIMUM=Value is less than allowable minimum of ''{0}''
ErrorMessages.properties
<application>
...
<message-bundle>ErrorMessages</message-bundle>
</application>
faces-config.xml
Obsługa zdarzeń w aplikacji JSF
• Kategorie zdarzeń w aplikacji JSF:
– zdarzenia inicjujące przetwarzanie po stronie logiki biznesowej – zdarzenia wpływające jedynie na interfejs użytkownika
• Kategorie „procedur” obsługi zdarzeń w JSF:
– Action controllers (metody akcji)
• zwracają wartości decydujące o nawigacji
– Event listeners
• nie wpływają bezpośrednio na nawigację
Rodzaje Event Listeners
• ActionListener
– wywoływany przez przyciski, mapy obrazkowe i linki z kodem JavaScript
– elementy te automatycznie zatwierdzają formularz
• ValueChangeListener
– wywoływany przez listy rozwijane, pola wyboru, grupy radiowe, pola tekstowe, itp.
– elementy te automatycznie nie zatwierdzają formularza
• konieczne wymuszenie zatwierdzenia formularza poprzez kod JavaScript
Cykl życia JSF
Restore View
postback initial
request
Render Response
Apply Request Values
Invoke Application
Process Validations
Update Model
exception
ActionListener - Przykład
<h:commandButton
actionListener= "#{bean.sideEffect}"
immediate="true" />
...
public void sideEffect(ActionEvent event) { // e.g. activatiing / deactivating form elements ...
} ...
Strona JSF
Klasa backing bean
ValueChangeListener: Przykład (1/2)
• Pole wyboru uaktywniające przycisk zatwierdzający formularz logowania
<h:commandButton value="Log in" ... disabled=”true” />
<h:selectBooleanCheckbox
binding="#{loginBean.selectBooleanCheckbox1}"
id="selectBooleanCheckbox"
valueChangeListener="#{loginBean.checkbox1Changed}"
onchange="submit()"/>
index.xhtml
• Problem „niechcianej” walidacji
ValueChangeListener: Przykład (2/2)
<h:selectBooleanCheckbox ...
immediate="true"/>
index.xhtml
LoginBean.java
public void checkbox1Changed(
ValueChangeEvent valueChangeEvent) { if (selectBooleanCheckbox1.isSelected())
commandButton1.setDisabled(false);
else commandButton1.setDisabled(true);
FacesContext context = FacesContext.getCurrentInstance();
context.renderResponse();
}
Internacjonalizacja aplikacji JSF (1/2)
<application>
<locale-config>
<default-locale>en</default-locale>
<supported-locale>pl</supported-locale>
</locale-config>
<message-bundle>ErrorMessages</message-bundle>
</application>
faces-config.xml
Internacjonalizacja aplikacji JSF (2/2)
sorryPrompt=Niestety
AppMessages_pl.properties
<f:view locale="#{facesContext.externalContext.request.locale}">
<f:loadBundle basename="AppMessages" var="msg"/>
... <h:outputText value="#{msg.sorryPrompt}"/>
<h:outputText value="#{loginBean.username}"/>
...
</f:view>
failure.xhtml
sorryPrompt=Sorry
AppMessages.properties
by default
or: #{msg['sorryPrompt']}
• Jeśli ten sam plik komunikatów jest używany na wszystkich stronach,
to zamiast przez f:loadBundle lepiej wskazać go w faces-config.xml
Prezentacja danych tabelarycznych
• Komponent h:dataTable umożliwia prezentację dynamicznej zawartości w formie tabelki HTML
– liczba wierszy tabeli nieznana na etapie projektowania strony – typowy scenariusz przy zapytaniach do bazy danych
– h:dataTable zawiera definicję struktury wiersza z danymi i opcjonalnie wiersza nagłówka i stopki
• Źródła danych dla h:dataTable:
– lista lub tablica komponentów JavaBean – pojedynczy komponent JavaBean
– java.sql.ResultSet
– javax.servlet.jsp.jstl.sql.Result
– javax.faces.model.DataModel
h:dataTable: Przykład (1/2)
@Named
@RequestScoped
public class ProductsList { …
public List<Product> getAllProducts() { …
} }
ProductsList.java
h:dataTable: Przykład (2/2)
…
<h:dataTable value="#{productsList.allProducts}" var="item" border="1">
<h:column>
<f:facet name="header">
<h:outputText value="Name"/>
</f:facet>
<h:outputText value="#{item.name}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Price"/>
</f:facet>
<h:outputText value="#{item.price}"/>
</h:column>
</h:dataTable>
…
productsList.xhtml
JSF w środowisku IDE (1/2)
• Graficzne edytory (Oracle JDeveloper)
• Kreator obsługi zdarzeń (Oracle JDeveloper)
JSF w środowisku IDE (2/2)
...
public String commandButton1_action() {
// Add event code here...
return null;
} ...
Dwuklik
na przycisku
Ajax w JSF 2.x
• JSF 2.x obsługuje Ajax poprzez wbudowaną bibliotekę JavaScript
• Biblioteka na może być używana na 2 sposoby:
– Poprzez znacznik <f:ajax>
• W połączeniu z innym standardowym komponentem
• Dodanie funkcjonalności Ajax bez kodowania w języku JavaScript
– Poprzez bezpośrednie wywołanie metody JavaScript API
jsf.ajax.request()
<f:ajax>: Przykład (1/2)
…
<h:head></h:head>
<h:body>
<h:form>
<h:outputText id="date" value="#{welcomeBean.currentDateTime}" />
<h:inputText id="name" value="#{welcomeBean.name}"></h:inputText>
<h:commandButton value="Submit">
<f:ajax execute="name" render="greeting" />
</h:commandButton>
<h:outputText id="greeting" value="#{welcomeBean.greeting}" />
</h:form>
</h:body>
…
index.xhtml
<f:ajax>: Przykład (2/2)
@Named
@RequestScoped
public class WelcomeBean { private String name;
... // public getName(), setName(...) public String getGreeting(){
return "Welcome " + name + "!";
}
public String getCurrentDateTime(){
return new java.util.Date().toString();
} }