10. Версія 5 — Веб-додаток PAM / JSF
10.1. Архітектура додатка
Архітектура веб-додатку PAM буде такою:
![]() |
У цій версії сервер Glassfish розміщуватиме всі рівні додатка:
- шар [web] розміщується в контейнері сервлетів сервера (1 нижче)
- інші рівні [metier, DAO, jpa] розміщуються у контейнері EJB3 сервера (2 нижче)
![]() |
Елементи [metier, DAO] додатка, що виконуються в контейнері EJB3, вже були описані в клієнтсько-серверному додатку, розглянутому в розділі 7.1, архітектура якого була такою:
![]() |
Рівні [metier, DAO] виконувалися в контейнері EJB3 сервера Glassfish, а рівень [ui] — у консольному додатку або додатку Swing на іншій машині:
![]() |
В архітектурі нового додатка:
![]() |
потрібно написати лише шар [web / jsf]. Інші шари [metier, DAO, jpa] вже є.
У документі [ref3] показано, що веб-додаток, у якому веб-шар реалізовано за допомогою Java Server Faces, має архітектуру, подібну до наведеної нижче:
![]() |
Ця архітектура реалізує шаблон проектування MVC (Модель, Вигляд, Контролер). Обробка запиту клієнта відбувається наступним чином:
Якщо запит надходить через GET, виконуються два наступні етапи:
- запит — браузер клієнта надсилає запит до контролера [Faces Servlet]. Цей контролер обробляє всі запити клієнтів. Це вхідна точка додатка. Це «C» у MVC.
- відповідь — контролер C запитує обрану сторінку JSF, щоб вона відобразилася. Це представлення, «V» у MVC. Сторінка JSF використовує шаблон M для ініціалізації динамічних частин відповіді, яку вона має надіслати клієнту. Цей шаблон — це клас Java, який може звертатися до рівня [métier] [4a], щоб надати представленню V необхідні дані.
Якщо запит надсилається за допомогою POST, між запитом і відповіддю додаються два додаткові етапи:
- запит — браузер-клієнт надсилає запит до контролера [Faces Servlet].
- обробка — контролер C обробляє цей запит. Адже запит POST супроводжується даними, які потрібно обробити. Для цього контролер використовує допомогу обробників подій, специфічних для написаного додатка [2a]. Цим обробникам можуть знадобитися дані бізнес-шару [2b]. Обробник події може бути змушений оновити певні моделі M [2c]. Після обробки запиту клієнта він може викликати різні відповіді. Класичним прикладом є:
- сторінка з повідомленням про помилку, якщо запит не вдалося обробити належним чином
- сторінка підтвердження в іншому випадку
Обробник події повертає контролеру [Faces Servlet] результат у вигляді символьного рядка, який називається ключем навігації.
- навігація — контролер обирає сторінку JSF (= представлення), яку потрібно надіслати клієнту. Цей вибір здійснюється на основі ключа навігації, повернутого обробником подій.
- відповідь — обрана сторінка JSF надішле відповідь клієнту. Вона використовує свій шаблон M для ініціалізації своїх динамічних частин. Ця модель також може звертатися до шару [métier] [4a], щоб надати сторінці JSF необхідні дані.
У проєкті JSF:
- контролером C є сервлет [javax.faces.webapp.FacesServlet]. Він знаходиться у бібліотеці [jsf-api.jar].
- види V реалізовані сторінками JSF.
- моделі M та обробники подій реалізовані класами Java, які часто називають «backing beans».
- У версіях JSF та 1.x визначення бінів, а також правила переходу між сторінками задано у файлі [faces-config.xml]. У ньому міститься перелік переглядів та правила переходу від одного до іншого. Починаючи з версії JSF 2, визначення бінів можна здійснювати за допомогою анотацій, а переходи між сторінками — «жорстко» у коді бінів.
10.2. Робота додатка
При першому запуску додатка відображається така сторінка:
![]() |
Потім заповнюємо форму та запитуємо інформацію про зарплату:
![]() |
Отримуємо такий результат:
![]() |
Ця версія розраховує уявну заробітну плату. Не слід звертати увагу на зміст сторінки, а лише на її оформлення. При натисканні кнопки [Raz] відбувається повернення на сторінку [A].
Помилки у введенні даних позначаються, як показано в наступному прикладі:
![]() |
10.3. Проєкт NetBeans
Ми створимо першу версію додатка, у якій буде імітуватися рівень [métier]. Ми матимемо таку архітектуру:
![]() |
Коли обробники подій або моделі запитуватимуть дані у шару [métier] [2b, 4a], цей шар надаватиме їм фіктивні дані. Мета полягає в тому, щоб отримати веб-шар, який коректно реагує на запити користувача. Коли це буде досягнуто, нам залишиться лише встановити серверний шар, розроблений у розділі 7.1:
![]() |
Це буде версія 2 веб-версії нашого додатка PAM.
Проект NetBeans для версії 1 — це такий проект Maven:
![]() |
- у [1], конфігураційні файли
- у [2], сторінки — у XHTML, а таблицю стилів
- у [3], класи шару — у [web]
- у [4] — об’єкти, що обмінюються між шаром [web] та шаром [métier], а також самим шаром [métier]
- у [5], файл повідомлень для інтернаціоналізації додатка
- у [6] — залежності додатка
Розглянемо деякі з цих елементів.
10.3.1. Конфігураційні файли
Файл [web.xml] — це файл, який за замовчуванням генерується NetBeans, а також містить налаштування сторінки винятків:
- рядок 30: [index.html] — це головна сторінка додатка
- рядки 32–39: налаштування сторінки винятків
Сторінка [exception.html] походить від [ref3]. Її код такий:
Будь-яке виключення, яке не обробляється явно кодом веб-додатка, призведе до відображення сторінки, подібної до наведеної нижче:
![]() |
Файл [faces-config.xml] матиме такий вигляд:
Зверніть увагу на наступні моменти:
- рядки 9–14: файл [messages.properties] використовуватиметься для інтернаціоналізації сторінок. Доступ до нього на сторінках XHTML здійснюватиметься за допомогою ключа msg.
- рядок 15: визначає файл [messages.properties] як такий, що має бути перевірений у першу чергу для повідомлень про помилки, які відображаються тегами <h:messages> та <h:message>. Це дозволяє перевизначити деякі повідомлення про помилки за замовчуванням з файлу JSF. Ця можливість тут не використовується.
10.3.2. Таблиця стилів
Файл [styles.css] має такий вигляд:
.libelle{
background-color: #ccffff;
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold
}
body{
background-color: #ffccff
}
.error{
color: #ff3333
}
.info{
background-color: #99cc00
}
.titreInfos{
background-color: #ffcc00
}
Ось приклади коду JSF, у якому використовуються ці стилі:
| |
| |
| ![]() |
10.3.3. Файл повідомлень
Файл повідомлень [messages_fr.properties] має такий вигляд:
form.titre=Feuille de salaire
form.comboEmployes.libell\u00e9=Employ\u00e9
form.heuresTravaill\u00e9es.libell\u00e9=Heures travaill\u00e9es
form.joursTravaill\u00e9s.libell\u00e9=Jours travaill\u00e9s
form.heuresTravaill\u00e9es.required=Indiquez le nombre d'heures travaill\u00e9es
form.heuresTravaill\u00e9es.validation=Donn\u00e9e incorrecte
form.joursTravaill\u00e9s.required=Indiquez le nombre de jours travaill\u00e9s
form.joursTravaill\u00e9s.validation=Donn\u00e9e incorrecte
form.btnSalaire.libell\u00e9=Salaire
form.btnRaz.libell\u00e9=Raz
exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=Url demand\u00e9e lors de l'erreur
exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
form.infos.employ\u00e9=Informations Employ\u00e9
form.employe.nom=Nom
form.employe.pr\u00e9nom=Pr\u00e9nom
form.employe.adresse=Adresse
form.employe.ville=Ville
form.employe.codePostal=Code postal
form.employe.indice=Indice
form.infos.cotisations=Informations Cotisations sociales
form.cotisations.csgrds=CSGRDS
form.cotisations.csgd=CSGD
form.cotisations.retraite=Retraite
form.cotisations.secu=S\u00e9curit\u00e9 sociale
form.infos.indemnites=Informations Indemnit\u00e9s
form.indemnites.salaireHoraire=Salaire horaire
form.indemnites.entretienJour=Entretien / Jour
form.indemnites.repasJour=Repas / Jour
form.indemnites.cong\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
form.infos.salaire=Informations Salaire
form.salaire.base=Salaire de base
form.salaire.cotisationsSociales=Cotisations sociales
form.salaire.entretien=Indemnit\u00e9s d'entretien
form.salaire.repas=Indemnit\u00e9s de repas
form.salaire.net=Salaire net
Усі ці повідомлення використовуються на сторінці [index.xhtml], за винятком тих, що містяться у рядках 11–15, які використовуються на сторінці [exception.xhtml].
10.3.4. Область дії бінів
Біан [web.forms.Form] матиме область дії request:
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
@ManagedBean
@RequestScoped
public class Form implements Serializable {
Біан [web.utils.ChangeLocale] матиме область дії «application»:
package web.utils;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
// локалізація сторінок
private String locale="fr";
public ChangeLocale() {
}
public String setFrenchLocale(){
locale="fr";
return null;
}
public String setEnglishLocale(){
locale="en";
return null;
}
public String getLocale() {
return locale;
}
public void setLocale(String locale) {
this.locale = locale;
}
}
10.3.5. Рівень [métier]
Рівень [métier] реалізує наступний інтерфейс IMetierLocal:
package metier;
import java.util.List;
import javax.ejb.Local;
import jpa.Employe;
@Local
public interface IMetierLocal {
// отримати розрахунковий лист
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// список співробітників
List<Employe> findAllEmployes();
}
Цей інтерфейс використовується в серверній частині клієнт-серверного додатка, описаного в розділі 7.1.
Клас Metier, який ми будемо використовувати для тестування шару [web], реалізує цей інтерфейс наступним чином:
package metier;
...
public class Metier implements IMetierLocal {
// словник співробітників, індексований за номером SS
private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
// список співробітників
private List<Employe> listEmployes;
// отримати платіжну відомість
public FeuilleSalaire calculerFeuilleSalaire(String SS,
double nbHeuresTravaillées, int nbJoursTravaillés) {
// отримати дані про працівника за номером SS
Employe e=hashEmployes.get(SS);
// повернути фіктивну платіжну відомість
return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),new ElementsSalaire(100,100,100,100,100));
}
// список співробітників
public List<Employe> findAllEmployes() {
if(listEmployes==null){
// створення списку з двох працівників
listEmployes=new ArrayList<Employe>();
listEmployes.add(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",new Indemnite(2,2.1,2.1,3.1,15)));
listEmployes.add(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",new Indemnite(1,1.93,2,3,12)));
// словник співробітників, індексований за номером SS
for(Employe e:listEmployes){
hashEmployes.put(e.getSS(),e);
}
}
// видається список співробітників
return listEmployes;
}
}
Розшифрувати цей код ми залишаємо на розсуд читача. Зверніть увагу на використаний підхід: щоб уникнути необхідності впроваджувати частину EJB додатка, ми імітуємо рівень [métier]. Коли рівень [web] буде визнано правильним, ми зможемо замінити його справжнім рівнем [métier].
10.4. Форма [index.xhtml] та її шаблон [Form.java]
Зараз ми створюємо сторінку XHTML форми, а також її шаблон.
Рекомендовані матеріали у [ref3]:
- приклад № 3 (mv-jsf2-03) — перелік тегів, які можна використовувати у формі
- приклад № 4 (mv-jsf2-04) — про випадаючі списки, що заповнюються з шаблону
- приклад № 6 (mv-jsf2-06) — перевірка введених даних
- приклад № 7 (mv-jsf2-07) — управління кнопкою [Raz]
10.4.1. крок 1
Завдання: Створити форму [index.xhtml] та її модель [Form.java], необхідні для отримання такої сторінки:
![]() |
Компоненти для введення даних такі:
id | тип JSF | шаблон | роль | |
comboEmployes | <h:selectOneMenu> | String comboEmployesValue List<Співробітник> getEmployes() | містить список співробітників у форматі «ім’я прізвище». | |
heuresTravaillees | <h:inputText> | String heuresTravaillées | кількість відпрацьованих годин — фактичне значення | |
joursTravailles | <h:inputText> | Строка joursTravaillés | кількість відпрацьованих днів — ціле число | |
btnSalaire | <h:commandButton> | запускає розрахунок заробітної плати | ||
btnRaz | <h:commandButton> | повертає форму до початкового стану |
- метод getEmployes поверне список співробітників, отриманий з рівня [métier]. Об’єкти, що відображаються у комбінованому списку, матимуть атрибут itemValue — номер співробітника SS, а також атрибут itemLabel — рядок, що складається з імені та прізвища співробітника.
- Кнопки [Salaire] та [Raz] наразі не будуть підключені до обробників подій.
- Буде перевірено правильність введених даних.

Випробуйте цю версію. Зокрема, переконайтеся, що помилки введення даних правильно позначаються.
Примітка: важливо, щоб атрибути id компонентів сторінки не містили символів з діакритичними знаками. У Glassfish 3.1.2 це призводить до збою програми.
10.4.2. Крок 2
Завдання: заповніть форму [index.xhtml] та її шаблон [Form.java], щоб після натискання кнопки [Salaire] з’явилася така сторінка:
![]() |
Кнопка [Salaire] буде пов’язана з обробником події calculerSalaire шаблону. Цей метод використовуватиме метод calculerFeuilleSalaire з шару [métier]. Ця платіжна відомість буде сформована для працівника, обраного в [1].
У шаблоні розрахунковий лист буде представлений таким приватним полем:
private FeuilleSalaire feuilleSalaire;
з методами get та set.
Щоб отримати інформацію, що міститься в цьому об’єкті, на сторінці JSF можна написати такі вирази:
<h:outputText value="#{form.feuilleSalaire.employe.nom}"/>
Вираз атрибута value буде обчислено наступним чином:
[form].getFeuilleSalaire().getEmploye().getNom(), де [form] представляє екземпляр класу [Form.java]. Читач може переконатися, що методи get, які тут використовуються, дійсно існують відповідно у класах [Form], [FeuilleSalaire] та [Employe]. Якби це не було так, під час обчислення виразу було б згенеровано виняток.
Перевірте цю нову версію.
10.4.3. Крок 3
Завдання: заповніть форму [index.xhtml] та її шаблон [Form.java], щоб отримати таку додаткову інформацію:
![]() |
Ми будемо дотримуватися того самого підходу, що й раніше. Виникає складність із символом валюти євро, який міститься, наприклад, у [1]. У рамках інтернаціоналізованого додатка було б краще мати формат відображення та валютний знак, що використовуються в locale (en, de, fr, ...). Це можна отримати таким чином:
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
</h:outputFormat>
Можна було б написати:
<h:outputText value="#{form.feuilleSalaire.employe.indemnite.entretienJour} є">
але з локаллю en_GB (англійська GB) відображенням продовжуватиме бути євро, тоді як слід було б використовувати фунт стерлінгів (£). Тег <h:outputFormat> дозволяє відображати інформацію відповідно до локалі locale сторінки JSF, що відображається:
- рядок 1: відображає параметр {0}, який є числом (number), що позначає грошову суму (currency)
- рядок 2: тег <f:param> присвоює значення параметру {0}. Другий тег <f:param> присвоїв би значення параметру {1} і так далі.
10.4.4. крок 4
Рекомендовані матеріали: приклад № 7 (mv-jsf2-07) у [ref3].
Завдання: заповніть форму [index.xhtml] та її шаблон [Form.java] для управління кнопкою [Raz].
Кнопка [Raz] повертає форму до того стану, в якому вона перебувала під час першого запиту за допомогою GET. Тут є кілька складнощів. Деякі з них було пояснено в [ref3].
Форма, що відображається після натискання кнопки [Raz], не є повною формою, а лише її частиною saisie:

Цей результат можна отримати за допомогою тегу <f:subview>, використаного таким чином:
<f:subview id="viewInfos" rendered="#{form.viewInfosIsRendered}">
... la partie du formulaire qu'on veut pouvoir ne pas afficher
</f:subview>
Тег <f:subview> охоплює всю ту частину форми, яку можна відображати або приховувати. Будь-який компонент можна відображати або приховувати за допомогою атрибута rendered. Якщо rendered="true", компонент відображається; якщо rendered="false", — ні. Якщо атрибут rendered отримує значення в шаблоні, відображення компонента можна контролювати програмно.
У наведеному вище прикладі відображення подання viewInfos буде керуватися за допомогою такого поля:
private boolean viewInfosIsRendered;
разом із відповідними методами get та set. Методи, що обробляють натискання на кнопки [Salaire] та [Raz], оновлюватимуть це булеве значення залежно від того, чи слід відображати вигляд viewInfos.

















