Практикум 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.
В новых 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 | Разметка и компоненты UI | index.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:commandButton | TodoBean.addTask() |
| Отметить "выполнено" | h:selectBooleanCheckbox, f:ajax | Task.setCompleted() |
| Удалить строку | h:commandButton | TodoBean.removeTask(task) |
| Отправить пустое поле | h:messages | FacesMessage в addTask() |
| Увидеть статистику | EL в разметке | tasks.size(), completedCount |
Карта этапов
| Этап | Фокус | Результат |
|---|---|---|
| 0 | Каркас Maven WAR | Пустая структура каталогов |
| 1 | pom.xml | Зависимости JSF, CDI, Jetty |
| 2 | web.xml, beans.xml | FacesServlet и CDI |
| 3 | Task | Модель задачи |
| 4 | TodoBean | Логика списка, @SessionScoped |
| 5 | index.xhtml, форма | Добавление и сообщения |
| 6 | Таблица и AJAX | h:dataTable, f:ajax |
| 7 | CSS | Оформление страницы |
| 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 нужны три группы библиотек:
- Servlet API — контракт HTTP-сервлета (реализацию даёт Jetty/Tomcat).
- JSF API + реализация (Mojarra) — компоненты, lifecycle, EL-интеграция.
- 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-core | CDI в "голом" servlet-контейнере без полного Java EE-сервера |
jakarta.enterprise.cdi-api | Аннотации @Named, @SessionScoped |
jakarta.inject-api | @Inject для будущего расширения |
Разбор сборки
<packaging>war</packaging>— итогmvn package→target/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—@SessionScopedbean и объекты в сессии могут сериализоваться между запросами; безserialVersionUIDпри изменении класса сессии "ломаются" после redeploy.titleиcreatedAt—final— после создания задачи заголовок и время не меняются; меняется только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() | Свойство completedCount | EL опускает 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".
Что происходит при нажатии "Добавить"
- Restore View — восстановление дерева компонентов (из ViewState или первичное построение).
- Apply Request Values —
newTaskTitle← текст из поля. - Process Validations — в этом примере валидаторов нет.
- Update Model Values — подтверждение значений в bean.
- Invoke Application —
todoBean.addTask(). - 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
Чек-лист ручного тестирования
- http://localhost:8080/ открывается без 404 — значит,
welcome-fileиFacesServletнастроены верно. - Кнопка "Добавить" с пустым полем — жёлтое предупреждение "Введите название задачи".
- Текст задачи появляется в таблице с датой в формате
dd.MM.yyyy HH:mm. - Чекбокс зачёркивает текст и увеличивает "Выполнено" без мигания всей страницы — работает
f:ajax. - "Удалить" убирает строку; при пустом списке видна подсказка "Список пуст…".
- F5 — задачи остаются — подтверждение
@SessionScopedи HTTP-сессии. - Новая вкладка браузера (или другой браузер) — отдельная сессия, список пуст (ожидаемо).
Альтернатива Jetty — Tomcat
После mvn package скопируйте target/jsf-simple.war в webapps Tomcat 10.1+ (Jakarta EE, не Tomcat 9 с javax.*). Приложение будет на http://localhost:8080/jsf-simple/ — контекст совпадает с именем WAR.
Импортируйте Maven-проект в IntelliJ IDEA, запустите конфигурацию "Jetty" или "Tomcat Local" и поставьте breakpoint в addTask() — на postback выполнение остановится в bean. Общие приёмы — Отладка Java-кода в IDE.
Частые ошибки
| Симптом | Вероятная причина | Что проверить |
|---|---|---|
404 на /index.xhtml | Нет servlet-mapping для FacesServlet | web.xml, шаблон *.xhtml |
EL todoBean not found | CDI не инициализирован | beans.xml, Weld listener, @Named |
PropertyNotFoundException | Нет getter/setter или опечатка в EL | JavaBean-имена, ООП и инкапсуляция |
ViewExpiredException | Истекла HTTP-сессия на сервере | Увеличить timeout или @SessionScoped + redirect |
| Стили не применяются | Неверный library или путь файла | resources/css/styles.css, library="css" |
| Чекбокс не обновляет счётчик | Нет f:ajax или узкий render | render="@form" или явный список id |
package jakarta.faces does not exist | JDK < 17 или не скачались зависимости | JDK 17+, mvn dependency:resolve |
| Кракозябры в тексте | Нет UTF-8 в проекте | project.build.sourceEncoding, заголовок ответа |
Куда развивать дальше
- Bean Validation —
@NotBlankнаnewTaskTitle,<f:validateBean/>, сообщения рядом с полем (валидация в JSF). - JPA и БД — сущность
@Entity Task,@PersistenceContext, список переживает перезапуск сервера (Hibernate и JPA, работа с БД). - Навигация — страницы
edit.xhtml,faces-config.xmlили implicit navigation по outcome. - PrimeFaces — богатые таблицы, диалоги, фильтры (первая программа с PrimeFaces).
- Сервисный слой — вынести логику из bean в
@ApplicationScoped TodoService(разделение UI и домена). - Современный REST-стек — для greenfield-проектов чаще Spring Boot; JSF остаётся навыком сопровождения legacy.
Связанные статьи
- JavaServer Faces — фреймворк для веб-интерфейсов — lifecycle, CDI, AJAX, PrimeFaces
- Первая программа на JavaServer Faces — счётчик, Maven-каркас, PrimeFaces
- JavaBeans — компонентная модель — свойства, события, связь с CDI
- Структура и сборки Java-проектов — WAR, Maven, плагины
- Stream API в Java —
filter,countвgetCompletedCount - Ключевые классы —
java.time—LocalDateTime, форматирование - HTTP как основа веб-интеграций — POST, сессия, postback
- Отладка Java-кода в IDE — breakpoint на action method
Legacy и веб на Java — JavaServer Faces — теория, Первая программа на JSF, Практикум "Список задач", Spring Framework, Первая программа на Spring Boot.