Перейти к основному содержимому

Практикум JSF — список задач

Разработчику Начальный уровень

О практикуме

Соберём веб-приложение "Список задач" на JavaServer Faces (JSF) 4 и Jakarta EE 10. Пользователь сможет добавлять задачи, отмечать их выполненными, удалять строки и видеть статистику — всё через браузер, без перезагрузки страницы при смене чекбокса.

Стек проекта:

  • Maven WAR — упаковка web-приложения для servlet-контейнера (структура и сборки);
  • Facelets — XHTML-разметка с тегами h: и f: (теория Facelets);
  • CDI (Weld) — создание и внедрение managed beans (JavaBeans и CDI);
  • Jetty Maven Plugin — локальный запуск без установки Tomcat.
Legacy, но учебно ценно

В новых backend-проектах чаще берут Spring Boot, а JSF встречается в корпоративных порталах и системах, которые поддерживают годами. Этот практикум не про "модный стек", а про понимание серверного MVC, postback и состояния формы на сервере — навыков, полезных и при чтении legacy-кода.

Что такое JSF в контексте этого проекта

JavaServer Faces (JSF) — спецификация Jakarta EE для построения пользовательского интерфейса на сервере. Браузер получает обычный HTML, но каждый клик по кнопке JSF-компонента отправляет postback — POST-запрос, который проходит через жизненный цикл JSF (шесть фаз от Restore View до Render Response). Подробно — в JavaServer Faces — фреймворк для веб-интерфейсов.

В нашем приложении три слоя MVC:

Слой MVCРоль в проектеФайлы
ModelДанные задачиTask.java
ViewРазметка и компоненты UIindex.xhtml, styles.css
ControllerОбработка действий пользователяTodoBean.java, FacesServlet

Связь View ↔ Model идёт через Expression Language (EL) — выражения вида #{todoBean.newTaskTitle}. JSF читает и записывает свойства bean по JavaBean-конвенции (ООП и классы).

Ключевые термины

  • Managed bean — Java-класс, живущий в определённой области видимости (request, session, view); в JSF 2.2+ это CDI-бины с @Named и @SessionScoped.
  • Postback — повторный POST на ту же страницу после действия пользователя (кнопка, чекбокс); типичный паттерн JSF, в отличие от REST, где каждый URL — отдельный ресурс (HTTP и веб-интеграции).
  • Facelets — шаблонизатор для .xhtml; теги h:inputText, h:commandButton превращаются в HTML на этапе Render Response.
  • WAR (Web Application Archive) — JAR-подобный архив с WEB-INF, classes и статикой; разворачивается в Tomcat, Jetty, Payara.
  • FacesServlet — единственная точка входа для всех *.xhtml; без него контейнер отдал бы файл как статику, минуя JSF.

Почему именно этот проект

  • охватывает типичный legacy-сценарий — форма + таблица + серверное состояние;
  • показывает связку managed bean ↔ EL ↔ компоненты JSF без сторонних UI-библиотек (PrimeFaces — в первой программе на JSF);
  • компактен для одного вечера (2–3 часа), но близок к реальной поддержке старых порталов;
  • после прохождения легче читать теорию JSF — вы уже видели lifecycle "вживую".

Предварительные знания: JDK 17+, Maven 3.8+, классы и коллекции (ООП, коллекции, при желании Stream API для getCompletedCount). Желательно пройти первую программу на JSF. Готовый проект для сверки: F:\Projects\JVM\Java\JavaServerFacesSimple.

Что получится

Действие пользователяКомпонент JSFМетод / свойство bean
Ввести текст и нажать "Добавить"h:inputText, h:commandButtonTodoBean.addTask()
Отметить "выполнено"h:selectBooleanCheckbox, f:ajaxTask.setCompleted()
Удалить строкуh:commandButtonTodoBean.removeTask(task)
Отправить пустое полеh:messagesFacesMessage в addTask()
Увидеть статистикуEL в разметкеtasks.size(), completedCount

Карта этапов

ЭтапФокусРезультат
0Каркас Maven WARПустая структура каталогов
1pom.xmlЗависимости JSF, CDI, Jetty
2web.xml, beans.xmlFacesServlet и CDI
3TaskМодель задачи
4TodoBeanЛогика списка, @SessionScoped
5index.xhtml, формаДобавление и сообщения
6Таблица и AJAXh:dataTable, f:ajax
7CSSОформление страницы
8Запускmvn jetty:run, проверка в браузере

После каждого этапа имеет смысл собрать или запустить проект — привычка из разработки и отладки: маленькие шаги, частая проверка.


Этап 0 — каркас проекта

Цель этапа — создать стандартную структуру WAR-проекта Maven.

Web-приложение на Java не начинается с public static void main (точка входа JVM). Точку входа задаёт servlet-контейнер: он читает WEB-INF/web.xml, поднимает сервлеты и слушатели, затем ждёт HTTP-запросы. Maven для таких проектов использует packaging war и каталог src/main/webapp — см. Структура и сборки Java-проектов.

Создайте корневую папку:

mkdir jsf-simple && cd jsf-simple

Каталоги (Windows PowerShell):

New-Item -ItemType Directory -Force -Path `
src/main/java/com/example/bean, `
src/main/java/com/example/model, `
src/main/webapp/WEB-INF, `
src/main/webapp/resources/css

Итоговая схема:

jsf-simple/
├── pom.xml
└── src/main/
├── java/com/example/
│ ├── bean/ ← TodoBean (controller)
│ └── model/ ← Task (model)
└── webapp/
├── index.xhtml ← view
├── WEB-INF/
│ ├── web.xml
│ └── beans.xml
└── resources/css/
└── styles.css

Разбор структуры

  • src/main/java — компилируемый Java-код; пакеты повторяют путь папок (com.example.bean).
  • src/main/webapp — корень web-приложения; отсюда контейнер раздаёт страницы и статику.
  • WEB-INF — защищённая зона: файлы внутри не доступны по прямому URL из браузера.
  • resources/css/ — соглашение JSF для статических ресурсов; library="css" в h:outputStylesheet указывает на эту папку.

Этап 1 — pom.xml

Цель этапа — подключить Jakarta Faces 4, CDI (Weld) и плагин Jetty для локального запуска.

Файл pom.xml (Project Object Model) описывает координаты артефакта, версии Java, зависимости и плагины сборки. Для JSF нужны три группы библиотек:

  1. Servlet API — контракт HTTP-сервлета (реализацию даёт Jetty/Tomcat).
  2. JSF API + реализация (Mojarra) — компоненты, lifecycle, EL-интеграция.
  3. CDI (Weld)@Named, @SessionScoped, discovery бинов.

Создайте pom.xml в корне проекта:

Код ITЗагрузка примера кода…

Разбор зависимостей

ЗависимостьНазначение
jakarta.servlet-api (provided)Типы HttpServlet, ServletContext; в WAR не кладётся — их даёт контейнер
jakarta.faces-apiИнтерфaces JSF — UIComponent, FacesContext
jakarta.faces (Mojarra)Реализация спецификации — парсер Facelets, рендереры HTML
weld-servlet-coreCDI в "голом" servlet-контейнере без полного Java EE-сервера
jakarta.enterprise.cdi-apiАннотации @Named, @SessionScoped
jakarta.inject-api@Inject для будущего расширения

Разбор сборки

  • <packaging>war</packaging> — итог mvn packagetarget/jsf-simple.war.
  • maven-war-plugin — упаковывает webapp и скомпилированные классы.
  • jetty-maven-plugin — команда mvn jetty:run поднимает приложение на http://localhost:8080/ без отдельной установки Tomcat.

Проверка:

mvn -q validate

Если Maven не находит артефакты — проверьте интернет и зеркала репозитория (первая программа на Java, раздел про Maven).


Этап 2 — web.xml и beans.xml

Цель этапа — зарегистрировать FacesServlet, включить CDI и указать стартовую страницу.

Два конфигурационных файла в WEB-INF задают поведение контейнера и CDI. Без них страница index.xhtml либо отдастся как статический XML, либо bean todoBean не будет найден в EL.

web.xml — дескриптор web-приложения

src/main/webapp/WEB-INF/web.xml:

Код ITЗагрузка примера кода…

Разбор web.xml

  • <listener> Weld — при старте приложения инициализирует CDI-контейнер; без него @Named не сработает.
  • FacesServlet — центральный контроллер JSF (раздел про FacesServlet); load-on-startup грузит его при деплое, а не при первом запросе.
  • url-pattern *.xhtml — любой запрос к index.xhtml проходит через lifecycle JSF, а не через default servlet.
  • welcome-file-list — при обращении к / контейнер перенаправит на index.xhtml.
  • PROJECT_STAGE=Development — подробные сообщения об ошибках в логах; в production ставят Production.
  • FACELETS_SKIP_COMMENTS=true — HTML-комментарии <!-- --> в .xhtml не попадают в вывод (удобно для учебных пометок в разметке).

beans.xml — активация CDI

src/main/webapp/WEB-INF/beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
bean-discovery-mode="all">
</beans>

Разбор beans.xml

  • Файл может быть пустым по содержанию, но его наличие говорит Weld сканировать классы приложения.
  • bean-discovery-mode="all" — CDI видит все классы с scope-аннотациями, не только с @Named.
  • Связь с JavaBeans — компонентная модель: managed bean в JSF 2.2+ — это CDI-бин с EL-именем.

Этап 3 — модель Task

Цель этапа — описать сущность задачи — заголовок, момент создания, флаг "выполнено".

Model в MVC — данные и правила предметной области. Task не знает про HTTP, XHTML и JSF; это обычный Java-класс. Такое разделение упрощает тесты и последующее подключение JPA (Hibernate и JPA — практический старт).

src/main/java/com/example/model/Task.java:

Код ITЗагрузка примера кода…

Разбор класса Task

  • implements Serializable@SessionScoped bean и объекты в сессии могут сериализоваться между запросами; без serialVersionUID при изменении класса сессии "ломаются" после redeploy.
  • title и createdAtfinal — после создания задачи заголовок и время не меняются; меняется только completed. Так проще рассуждать о состоянии строки таблицы.
  • LocalDateTime — современный API даты/времени (java.time); форматирование вынесено в getFormattedCreatedAt(), чтобы XHTML не вызывал DateTimeFormatter напрямую.
  • isCompleted / setCompleted — JavaBean-имена для boolean-свойства; JSF и EL обращаются к нему как #{task.completed}.
  • getFormattedCreatedAt() — "вычисляемое свойство" для View: в EL пишем #{task.formattedCreatedAt}, без скобок и без знания формата на странице.

Этап 4 — managed bean TodoBean

Цель этапа — инкапсулировать список задач и действия "добавить" / "удалить".

Controller в нашем приложении — TodoBean. Он хранит коллекцию Task, принимает текст новой задачи, валидирует ввод и отдаёт данные представлению через getters. Это managed bean в области session — один экземпляр на пользователя, пока жива HTTP-сессия (сессия и postback).

src/main/java/com/example/bean/TodoBean.java:

Код ITЗагрузка примера кода…

Разбор аннотаций

  • @Named("todoBean") — регистрирует бин в CDI и задаёт имя для EL; на странице пишем #{todoBean...}, не полное имя класса.
  • @SessionScoped — бин создаётся при первом обращении и живёт в HTTP-сессии; список задач сохраняется между postback-запросами и перезагрузками страницы (F5), пока не истечёт сессия.
  • Альтернативы из теории JSF: @RequestScoped (новый бин на каждый запрос), @ViewScoped (на одну JSF-страницу) — для todo-списка логична именно session.

Разбор методов

МетодТип в JSFПоведение
addTask()Action methodВызывается кнопкой; void — остаёмся на той же странице
removeTask(Task)Action method с параметромJSF сопоставляет строку dataTable с объектом Task
getTasks()Свойство tasksПитает h:dataTable value="#{todoBean.tasks}"
getCompletedCount()Свойство completedCountEL опускает get и ()
get/setNewTaskTitleДвусторонняя привязкаh:inputText value="#{todoBean.newTaskTitle}"

Разбор addTask()

  • newTaskTitle.isBlank() — проверка пустого ввода (строки в Java); при ошибке добавляется FacesMessage с уровнем WARN.
  • Первый аргумент addMessage(null, ...)глобальное сообщение; его покажет h:messages globalOnly="true".
  • После успешного добавления newTaskTitle = null — поле ввода очистится при следующем Render Response.

Разбор getCompletedCount()

  • Использует Stream API: filter(Task::isCompleted).count().
  • Вызывается при каждом рендере блока статистики — для учебного списка это нормально; в больших таблицах счётчик кешируют или пересчитывают по событию.

Сборка:

mvn -q compile

Этап 5 — страница index.xhtml, форма

Цель этапа — сверстать форму добавления и вывод сообщений. Таблицу добавим на следующем этапе.

View — Facelets-страница .xhtml. Теги с префиксом h: — HTML-компоненты JSF (inputText, commandButton, form); f: — ядро (валидаторы, AJAX, facet'ы заголовков колонок). Пространства имён Jakarta Faces 4: xmlns:h="jakarta.faces.html", xmlns:f="jakarta.faces.core" (Facelets и префиксы).

Создайте src/main/webapp/index.xhtml:

Код ITЗагрузка примера кода…

Разбор разметки

  • <h:form> — обязательная обёртка для интерактивных компонентов. При submit браузер отправляет POST (postback) с скрытыми полями javax.faces.ViewState — сериализованное состояние дерева компонентов.
  • value="#{todoBean.newTaskTitle}"двусторонняя привязка (two-way binding):
    • на фазе Apply Request Values JSF записывает текст из input в bean;
    • на Render Response — подставляет значение из bean в HTML.
  • action="#{todoBean.addTask}" — на фазе Invoke Application вызывается метод без аргументов.
  • h:messages globalOnly="true" — выводит только сообщения, добавленные с clientId = null (наши предупреждения о пустом поле).
  • styleClass — JSF-аналог HTML-атрибута class; рендерер превратит его в class="task-input".

Что происходит при нажатии "Добавить"

  1. Restore View — восстановление дерева компонентов (из ViewState или первичное построение).
  2. Apply Request ValuesnewTaskTitle ← текст из поля.
  3. Process Validations — в этом примере валидаторов нет.
  4. Update Model Values — подтверждение значений в bean.
  5. Invoke ApplicationtodoBean.addTask().
  6. Render Response — HTML страницы с обновлённым полем и сообщением (если было).

Подробная таблица фаз — в теории JSF.

Запуск:

mvn jetty:run

Откройте http://localhost:8080/. Задачу можно добавить, но список пока не виден — модель и bean уже работают; не хватает только h:dataTable на следующем этапе.


Этап 6 — таблица, статистика и AJAX

Цель этапа — вывести список в h:dataTable и обновлять "выполнено" без полной перезагрузки страницы.

h:dataTable — итератор по коллекции: атрибут value указывает на List, var задаёт имя строки для EL. f:ajax встроен в JSF 2+ (AJAX в JSF) и запускает сокращённый lifecycle только для указанных компонентов.

Замените содержимое <h:form>...</h:form> в index.xhtml:

Код ITЗагрузка примера кода…

Разбор таблицы и EL

  • #{todoBean.tasks.size()} — в EL можно вызывать методы без аргументов; size() списка обновляется после каждого add/remove.
  • rendered="#{not empty todoBean.tasks}" — компонент не рендерится, если список пуст; это JSF-way условного UI (предпочтительнее JSTL <c:if> внутри формы — см. JSTL и JSF).
  • var="task" — внутри строк таблицы #{task.title} ссылается на текущий элемент List<Task>.
  • value="#{task.completed}" на чекбоксе — двусторонняя привязка к Task.setCompleted(); при AJAX postback JSF найдёт тот же объект в session-scoped списке.

Разбор AJAX

<f:ajax event="change" render="@form"/>
  • event="change" — AJAX срабатывает при переключении чекбокса.
  • render="@form" — после Invoke Application перерисовать всю форму — статистику "Выполнено", зачёркивание текста, счётчик "Всего".
  • Без f:ajax пользователю пришлось бы нажимать отдельную кнопку "Обновить" — полный postback.

Разбор удаления

  • action="#{todoBean.removeTask(task)}" — JSF передаёт в метод объект строки, с которой связана кнопка.
  • После удаления lifecycle доходит до Render Response — таблица перестраивается без удалённой строки.

Разбор пустого состояния

  • h:panelGroup layout="block" — рендерит <div> с текстом-подсказкой.
  • rendered="#{empty todoBean.tasks}" — виден только когда таблица скрыта; пользователь не видит пустую <table>.

Этап 7 — стили CSS

Цель этапа — оформить страницу и подключить таблицу стилей через JSF, а не жёстким <link>.

Статические ресурсы JSF лежат в src/main/webapp/resources/<library>/. Компонент h:outputStylesheet генерирует URL с хешем версии — браузер корректно кеширует CSS при redeploy.

В <h:head> добавьте сразу после <title>:

<h:outputStylesheet library="css" name="styles.css"/>

Создайте src/main/webapp/resources/css/styles.css:

Код ITЗагрузка примера кода…

Разбор CSS и JSF

  • Класс .completed связан с EL на span: class="#{task.completed ? 'completed' : ''}" — при AJAX-обновлении формы зачёркивание появляется без F5.
  • .messages — стилизует <ul>, который рендерит h:messages.
  • h:outputStylesheet отдаёт URL вида /javax.faces.resource/styles.css.xhtml?ln=css — не путайте с прямым путём /resources/css/styles.css, который в JSF может не сработать без resource handler.

Общие принципы вёрстки форм — в веб-разделе; здесь CSS минимален и не претендует на дизайн-систему.


Этап 8 — запуск и проверка

Цель этапа — убедиться, что WAR собирается и все пользовательские сценарии работают.

mvn clean package
mvn jetty:run

Чек-лист ручного тестирования

  1. http://localhost:8080/ открывается без 404 — значит, welcome-file и FacesServlet настроены верно.
  2. Кнопка "Добавить" с пустым полем — жёлтое предупреждение "Введите название задачи".
  3. Текст задачи появляется в таблице с датой в формате dd.MM.yyyy HH:mm.
  4. Чекбокс зачёркивает текст и увеличивает "Выполнено" без мигания всей страницы — работает f:ajax.
  5. "Удалить" убирает строку; при пустом списке видна подсказка "Список пуст…".
  6. F5 — задачи остаются — подтверждение @SessionScoped и HTTP-сессии.
  7. Новая вкладка браузера (или другой браузер) — отдельная сессия, список пуст (ожидаемо).

Альтернатива Jetty — Tomcat

После mvn package скопируйте target/jsf-simple.war в webapps Tomcat 10.1+ (Jakarta EE, не Tomcat 9 с javax.*). Приложение будет на http://localhost:8080/jsf-simple/ — контекст совпадает с именем WAR.

Отладка в IDE

Импортируйте Maven-проект в IntelliJ IDEA, запустите конфигурацию "Jetty" или "Tomcat Local" и поставьте breakpoint в addTask() — на postback выполнение остановится в bean. Общие приёмы — Отладка Java-кода в IDE.


Частые ошибки

СимптомВероятная причинаЧто проверить
404 на /index.xhtmlНет servlet-mapping для FacesServletweb.xml, шаблон *.xhtml
EL todoBean not foundCDI не инициализированbeans.xml, Weld listener, @Named
PropertyNotFoundExceptionНет getter/setter или опечатка в ELJavaBean-имена, ООП и инкапсуляция
ViewExpiredExceptionИстекла HTTP-сессия на сервереУвеличить timeout или @SessionScoped + redirect
Стили не применяютсяНеверный library или путь файлаresources/css/styles.css, library="css"
Чекбокс не обновляет счётчикНет f:ajax или узкий renderrender="@form" или явный список id
package jakarta.faces does not existJDK < 17 или не скачались зависимостиJDK 17+, mvn dependency:resolve
Кракозябры в текстеНет UTF-8 в проектеproject.build.sourceEncoding, заголовок ответа

Куда развивать дальше

  1. Bean Validation@NotBlank на newTaskTitle, <f:validateBean/>, сообщения рядом с полем (валидация в JSF).
  2. JPA и БД — сущность @Entity Task, @PersistenceContext, список переживает перезапуск сервера (Hibernate и JPA, работа с БД).
  3. Навигация — страницы edit.xhtml, faces-config.xml или implicit navigation по outcome.
  4. PrimeFaces — богатые таблицы, диалоги, фильтры (первая программа с PrimeFaces).
  5. Сервисный слой — вынести логику из bean в @ApplicationScoped TodoService (разделение UI и домена).
  6. Современный REST-стек — для greenfield-проектов чаще Spring Boot; JSF остаётся навыком сопровождения legacy.

Связанные статьи


Legacy и веб на JavaJavaServer Faces — теория, Первая программа на JSF, Практикум "Список задач", Spring Framework, Первая программа на Spring Boot.