Jakarta Faces
Jakarta Faces (раньше JSF) — технология серверных HTML-страниц из компонентов. Страница описывается в XHTML-файле, данные и действия берутся из бина, а сервер сам собирает HTML, принимает формы и проверяет поля. Новые интерфейсы сегодня чаще делают на JavaScript поверх REST API, но на Faces написано множество корпоративных систем, и поддерживать их приходится до сих пор. Этот урок — знакомство с основами.
Включение Faces
Faces включается аннотацией @FacesConfig на любом бине приложения:
// src/main/java/com/example/blog/FacesActivator.java
package com.example.blog;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.faces.annotation.FacesConfig;
@FacesConfig
@ApplicationScoped
public class FacesActivator {
}Бин страницы
Странице нужен объект, из которого она берёт данные. Аннотация @Named делает бин доступным на странице по имени postsPage — имени класса с маленькой буквы:
// src/main/java/com/example/blog/PostsPage.java
package com.example.blog;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.List;
@Named
@RequestScoped
public class PostsPage {
@Inject
PostRepository repository;
@Inject
PostService service;
private String title;
private String body;
public List<Post> getPosts() {
return repository.findAll();
}
public String publish() {
service.publish(new NewPost(title, body));
return "posts?faces-redirect=true";
}
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getBody() { return body; }
public void setBody(String body) { this.body = body; }
}Поля формы связываются с парой геттер–сеттер. Метод publish вызывается кнопкой и возвращает, куда перейти: faces-redirect=true просит браузер заново загрузить страницу, чтобы обновление не отправило форму второй раз.
Страница
Создайте файл src/main/webapp/posts.xhtml:
<!-- src/main/webapp/posts.xhtml -->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:ui="jakarta.faces.facelets">
<h:head>
<title>Записи блога</title>
</h:head>
<h:body>
<h1>Записи блога</h1>
<ul>
<ui:repeat value="#{postsPage.posts}" var="post">
<li>#{post.title} (#{post.createdAt})</li>
</ui:repeat>
</ul>
<h2>Новая запись</h2>
<h:form id="postForm">
<h:outputLabel for="title" value="Заголовок" />
<h:inputText id="title" value="#{postsPage.title}" required="true" />
<h:outputLabel for="body" value="Текст" />
<h:inputTextarea id="body" value="#{postsPage.body}" />
<h:commandButton value="Опубликовать" action="#{postsPage.publish}" />
<h:messages />
</h:form>
</h:body>
</html>Выражения #{...} — язык выражений (Expression Language): #{postsPage.posts} вызывает getPosts(), #{postsPage.publish} — метод publish. Префиксы h: и ui: подключают библиотеки компонентов.
Результат
Откройте http://localhost:8080/blog/posts.xhtml. Сервер отдаст обычный HTML — список и форму:
<ul>
<li>Привет, Jakarta EE (2026-10-01)</li>
<li>Сервлеты и фильтры (2026-10-02)</li>
</ul>
<h2>Новая запись</h2><form id="postForm" name="postForm" method="post" action="/blog/posts.xhtml" ...>Заполните форму и нажмите «Опубликовать» — запись сохранится через тот же PostService, что и в REST API, и появится в списке. Если оставить заголовок пустым, required="true" не пропустит форму, а h:messages покажет сообщение об ошибке. На сервере с английской локалью оно выглядит так: postForm:title: Validation Error: Value is required.
Основные компоненты
| Компонент | Что выводит |
|---|---|
h:form | Форма |
h:inputText, h:inputTextarea | Поле ввода и многострочное поле |
h:commandButton | Кнопка, которая вызывает метод бина |
h:outputText | Текст с экранированием HTML |
h:dataTable | Таблица по списку |
h:messages | Сообщения об ошибках |
ui:repeat | Повторение разметки для каждого элемента |
Частая ошибка
Создать .xhtml-страницу, но не включить Faces. Без @FacesConfig (или файла WEB-INF/faces-config.xml) сервер отдаёт файл как есть, и браузер получает исходный текст с тегами h:body и выражениями #{postsPage.posts} вместо списка записей.