Бины в Jakarta EE¶
Бин - Это объект, которым управляет контейнер: создаёт его, внедряет зависимости, ведёт жизненный цикл. В Jakarta EE несколько «семейств» управляемых компонентов, у каждого свой контейнер и своя роль.
Note
Три похожих названия - три разные вещи: - JavaBean - соглашение о том, как написан класс. Контейнер им не управляет - Enterprise JavaBean (EJB) - компонент EJB-контейнера: транзакции, пул, таймеры (см. EJB) - CDI-бин (managed bean) - объект CDI-контейнера, основная модель бинов в Jakarta EE
Отдельный контейнер со своими бинами - у Spring.
JavaBean¶
JavaBean - Это обычный Java-класс, который следует соглашениям, чтобы инструменты могли работать с его
свойствами по именам: JSP-теги, EL, JSF, сериализация.
- Конструктор по умолчанию - публичный, без параметров
- Приватные свойства - все поля private
- Геттеры и сеттеры - публичные getName(), setName(), для boolean - isActive()
- Сериализация - может реализовывать Serializable, чтобы сохранять состояние, например в сессии
public class User implements Serializable {
private String name;
private int age;
public User() {}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
См. JavaBeans в JSP.
CDI-бины¶
CDI-бин (Contexts and Dependency Injection) - Это класс, которым управляет CDI-контейнер: внедрение
зависимостей, скоупы, квалификаторы, события, продюсеры, перехватчики, декораторы. Лёгкий и гибкий, сейчас это
основной выбор.
- любой POJO становится CDI-бином, если у него есть bean-defining аннотация (обычно scope) и он лежит в bean
archive (см. beans.xml)
- клиент получает не сам объект, а прокси. Бин с @ApplicationScoped создаётся при первом вызове метода
import jakarta.enterprise.context.ApplicationScoped; // scope CDI
import jakarta.inject.Inject; // внедрение CDI
@ApplicationScoped // делает класс CDI-бином, один экземпляр на всё приложение
public class VehicleService {
@Inject
VehicleDao vehicleDao; // DAO - тоже CDI-бин
public void createVehicle(VehicleDto dto) {
// бизнес-логика
}
}
Транзакции на CDI-бине включаются @Transactional, где его ставить - в
Где ставить @Transactional.
Скоупы¶
Жизненный цикл CDI-бина определяет scope:
| Scope | Живёт | Для чего |
|---|---|---|
@ApplicationScoped |
Всё время работы приложения, один экземпляр | Сервисы, DAO, hub'ы рассылки |
@RequestScoped |
Один HTTP-запрос | Контроллеры и ресурсы, которым нужно состояние на время запроса |
@SessionScoped |
Одна HTTP-сессия | Состояние пользователя в веб-приложении, используют редко |
@ConversationScoped |
Управляемая «длинная» беседа, несколько запросов | Сценарии из JSF, используют редко |
@Dependent |
Столько же, сколько бин-владелец. Скоуп по умолчанию, своего контекста нет | Использовать осознанно: у каждого владельца свой экземпляр |
new → @PostConstruct (при первом использовании в своём scope) → рабочее состояние → @PreDestroy (конец scope)
Возможности CDI¶
- Внедрение:
@Injectв поля, конструкторы, сеттеры - Имя для EL и JSF:
@Named("name"), без имени - имя класса с маленькой буквы - Квалификаторы:
@Qualifier+ свои аннотации, чтобы различать реализации (например@PrimaryDb) - Альтернативы:
@Alternative- подменить реализацию, например на заглушку в тестах - Продюсеры:
@Producesсоздаёт бин или ресурс,@Disposesзакрывает его - Стереотипы:
@Stereotypeсобирает набор аннотаций в одну - Перехватчики:
@Interceptor+@AroundInvokeили@AroundConstruct, включаются через@Priorityилиbeans.xml - Декораторы:
@Decoratorоборачивает вызовы бина и добавляет поведение - События:
Event<T>+@Observes, асинхронно -@ObservesAsync - Расширения: portable extensions (SPI) - менять и добавлять бины при старте контейнера
- Транзакции:
@Transactionalизjakarta.transaction(см. JTA) - Безопасность: Jakarta Security или средства сервера, на методах те же
@RolesAllowed
beans.xml и обнаружение бинов¶
beans.xml - Это файл настройки CDI для архива: какие классы считать бинами и что включить.
- в WAR лежит в WEB-INF/beans.xml, в JAR - в META-INF/beans.xml. В Maven-проекте это
src/main/webapp/WEB-INF/beans.xml или src/main/resources/META-INF/beans.xml
- с CDI 1.1 (Java EE 7) CDI включён и без beans.xml: архив считается implicit bean archive, и бинами становятся
классы с bean-defining аннотациями. Это требование спецификации, а не особенность WildFly или Payara
Режимы обнаружения (bean-discovery-mode):
- annotated - бины только классы с bean-defining аннотациями: scope, @Dependent, @Interceptor, @Decorator,
стереотип
- all - бин любой подходящий класс, даже без аннотаций
- none - CDI для архива выключен
<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"
version="4.0" bean-discovery-mode="annotated">
<alternatives>
<class>com.example.MockPaymentGateway</class> <!-- включить альтернативу -->
</alternatives>
</beans>
Когда beans.xml нужен:
- бинами должны стать классы без аннотаций - режим all
- включить @Alternative, если у неё нет @Priority
- задать порядок перехватчиков и декораторов, если у них нет @Priority
- исключить классы из сканирования - <scan><exclude>
- выключить CDI для архива - режим none
Когда не нужен: - бины помечены scope-аннотациями и особых настроек нет. Так устроено большинство проектов - в проекте нет CDI, например только сервлеты
Warning
Пустой beans.xml до CDI 4.0 означал режим all. С CDI 4.0 (Jakarta EE 10) пустой файл - это annotated. При
переезде на Jakarta EE 10 классы без аннотаций перестают быть бинами.
Note
В исходной версии было «без beans.xml контейнер не будет обрабатывать @Inject» и «beans.xml нужен, чтобы
интегрировать CDI с EJB и JAX-RS». Так было до Java EE 7. Сейчас CDI работает без файла, а для интеграции с EJB и
JAX-RS он не нужен.
EJB и другие управляемые компоненты¶
EJB - бины EJB-контейнера: @Stateless, @Stateful, @Singleton, @MessageDriven. Дают «из коробки»
транзакции, пул, таймеры, асинхронность, безопасность, удалённые вызовы. Session bean - одновременно и CDI-бин:
в нём работают @Inject и @Observes.
Остальные компоненты, которыми управляет контейнер:
| Компонент | Аннотации и интерфейсы | Когда применять |
|---|---|---|
| JAX-RS ресурс | @Path, @GET, @POST, @PUT, @DELETE, @Consumes, @Produces |
REST API для фронтенда или интеграции, например /api/vehicles. С CDI это CDI-бин, по умолчанию живёт один запрос |
| Servlet | @WebServlet |
Низкоуровневый HTTP, если не хватает JAX-RS. Например, отдача бинарных файлов (см. Java Servlets) |
| Filter | @WebFilter |
Перехват запросов и ответов: CORS, логирование, аутентификация |
| Listener | @WebListener |
События контейнера: создание и удаление сессий, запуск и остановка приложения |
| WebSocket endpoint | @ServerEndpoint |
Реалтайм: чат, уведомления, обновление таблиц в UI. Внутрь можно внедрять CDI- и EJB-бины |
| JSF / MVC бин | @Named, @ViewScoped, @SessionScoped, @RequestScoped |
Данные прямо в представление: #{bean.property}. Это обычный CDI-бин с @Named |
| Batch | @BatchProperty, ItemReader, ItemWriter, Job и Step в XML |
Массовая обработка данных: ETL, миграции, отчёты |
| JCA connector | @Connector, @ActivationSpec |
Интеграция с внешними системами через Resource Adapter, напрямую редко |
JSF Managed Beans - @ManagedBean и скоупы из jakarta.faces.bean.*. Удалены в Jakarta Faces 4.0, вместо них
CDI + @Named и JSF-скоупы (@ViewScoped из jakarta.faces.view) или обычные CDI-скоупы. Как их писали раньше -
в JSF.
Что бинами не является¶
- DTO - обычный POJO для входа и выхода API. Это транспорт, контейнер им не управляет
- JPA Entities -
@Entity,@Embeddable,@MappedSuperclass. Ими управляетEntityManager, а не CDI. Только данные и простая логика (см. JPA) - Bean Validation - не вид бина, а аннотации-ограничения
@NotNull,@Size,@Positive,@Pattern,@Email. Ставятся на поля DTO, сущностей и параметры. Проверку запускают JAX-RS, JPA перед сохранением и CDI - Утилиты - чистые функции и константы:
finalкласс, приватный конструктор, только статические методы (или Lombok@UtilityClass). Внедрять нечего, состояния нет
CDI или EJB¶
Оба - управляемые контейнером объекты, но с разной задачей. CDI отвечает за DI, жизненный цикл и контексты. EJB даёт готовый enterprise-набор: транзакции, безопасность, таймеры, распределённость.
| CDI | EJB | |
|---|---|---|
| Сколько экземпляров | Задано scope, скоупы гибкие | Задано контрактом, три варианта: пул, на клиента, один |
| Транзакции | Только с @Transactional (см. JTA) |
Каждый метод по умолчанию REQUIRED |
| Конкурентность | Не управляется, @ApplicationScoped должен быть потокобезопасным сам |
Один вызов на экземпляр, @Lock у singleton |
| Пул и пассивация | Нет. @SessionScoped сериализуется вместе с сессией |
Есть |
| Таймеры, асинхронность | Через Jakarta Concurrency | @Schedule, @Asynchronous |
| Безопасность | Jakarta Security или перехватчики | Встроена: @RolesAllowed, @PermitAll |
| Удалённый доступ | Нет | @Remote, сейчас почти не используют |
| События | @Observes, @ObservesAsync |
Тоже есть: session bean - это ещё и CDI-бин |
| Вес | Лёгкие, современный стандарт | Тяжелее и жёстче, исторический багаж «старого монолита» |
Как делают сейчас (Jakarta EE 10+) - CDI-first: сервисы, DAO, фильтры - CDI-бины. EJB берут точечно, когда
нужна их специфика:
- таймеры - @Singleton + @Schedule
- асинхронные вызовы - @Asynchronous, хотя есть Jakarta Concurrency и ManagedExecutorService
- MDB, если используется старое JMS API
- remote-вызовы между JVM
Note
В исходной версии у EJB в событиях стоял «❌», а среди причин брать EJB были «асинхронные методы с retry». Session
bean - это ещё и CDI-бин, поэтому @Observes в нём работает. Retry у @Asynchronous нет.
Итого: нужны сервисы, DAO, DI и транзакции - CDI + @Transactional. Нужны таймеры, асинхронность, декларативная
безопасность, пул - EJB. Тренд - CDI-first: возможности EJB переезжают в CDI и отдельные спецификации, например
@Transactional из JTA и @Asynchronous из Jakarta Concurrency.
Какой бин для какого слоя¶
Что делает каждый слой:
| Слой | Пакет | Что делает | Чего там быть не должно |
|---|---|---|---|
| Представление | controller |
JAX-RS ресурсы, иногда MVC или JSF. Принять HTTP-запрос, провалидировать вход (Bean Validation на DTO), разобрать параметры, пагинацию, фильтры, вызвать сервис, собрать ответ, статус, заголовки | Бизнес-логики и доступа к БД |
| Представление | ws |
WebSocket endpoints и hub. Получить событие от сервиса и разослать подписчикам: всем или выборочно | Работы с БД и бизнес-правил |
| Бизнес-логика | service |
Use-case-сервисы и фасады: бизнес-правила, граница транзакции, координация нескольких DAO и внешних интеграций, публикация событий (например, wsHub.broadcast(...) после сохранения) |
JPA-кода: Criteria, HQL, EntityManager - это DAO |
| Доступ к данным | dao |
Репозитории над JPA и Hibernate: CRUD, запросы (Criteria, HQL, native, хранимые процедуры), пагинация. Возвращают сущности или узкие проекции | Бизнес-правил |
| Доступ к данным | model |
JPA-сущности и value objects (@Embeddable): маппинг на таблицы, базовые инварианты данных, @PrePersist |
Логики, которой нужна инфраструктура: сеть, файлы, DAO. Лёгкие доменные методы можно |
| Контракты | dto |
Вход и выход API: срезы полей для UI и интеграций, валидация входа, без ленивых связей | Логики |
| Сквозное | filter |
JAX-RS ContainerRequestFilter и ContainerResponseFilter, servlet-фильтры: аутентификация, авторизация, трейсинг, CORS, аудит, логирование, метрики |
Бизнес-логики |
| Сквозное | util |
Чистые утилиты: форматтеры и парсеры дат, конвертеры, константы | Состояния и зависимостей |
В сервисе уместно: @Transactional, проверка бизнес-инвариантов, идемпотентность, блокировки (optimistic и
pessimistic, см. JTA), доменные ошибки, которые ExceptionMapper переводит в HTTP-ответ.
Дополнительные пакеты по необходимости:
- exception - доменные исключения и @Provider ExceptionMapper, который отдаёт единый JSON-ответ об ошибке
- config - CDI-продюсеры конфигурации, настройки в @ApplicationScoped-бинах
- events и interceptors - CDI-события для отложенных реакций, перехватчики для сквозной логики
- mapper - преобразование Entity ⇄ DTO вручную или через MapStruct
util лучше дробить по доменам: util.time, util.json, util.aggrid. Иначе он превращается в свалку.
Каким бином сделать каждый слой:
| Слой | Тип | Основные аннотации | Транзакции |
|---|---|---|---|
controller |
CDI-бин | @Path, @RequestScoped (или @ApplicationScoped, если состояния нет), @Produces, @Consumes, @Inject |
Нет |
ws |
Endpoint + CDI-hub | @ServerEndpoint + @ApplicationScoped hub |
Нет |
service |
CDI-бин | @ApplicationScoped (или @Dependent), @Transactional, @Inject |
Да, здесь граница |
dao |
CDI-бин | @ApplicationScoped, @PersistenceContext |
Участвует в транзакции сервиса |
model |
Не бин, JPA | @Entity, @Embeddable |
- |
dto |
POJO | Bean Validation: @NotNull, @Size |
- |
filter |
JAX-RS провайдер, CDI | @Provider, @Priority, @Inject |
Нет |
util |
Не бин | final + приватный конструктор или @UtilityClass |
- |
См. Где ставить @Transactional.
controller - ресурсы JAX-RS по спецификации совместимы с CDI. Бизнес-логика живёт вне контроллера.
@RequestScoped
@Path("/vehicle")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class VehicleController {
@Inject
VehicleService service;
// endpoints...
}
ws - в endpoint'е логика приёма и отправки. Общую рассылку и хранение Session держат в отдельном
CDI-синглтоне.
@ApplicationScoped
public class VehicleWsHub {
// хранение сессий, broadcast(...)
}
@ServerEndpoint("/ws/vehicle")
public class VehicleEndpoint {
@Inject
VehicleWsHub hub; // CDI-внедрение в WebSocket endpoint работает в современных серверах
// onOpen, onMessage, onClose - делегируют hub
}
service - пример выше, в CDI-бины.
dao - инкапсулирует JPA и Hibernate. Транзакцию открывает сервис, DAO в ней участвует. Пустой
результат DAO возвращает как Optional (см. Запросы в DAO и Optional).
@ApplicationScoped
public class VehicleDao {
@PersistenceContext(unitName = "studsPU")
EntityManager em;
public Vehicle save(Vehicle v) {
if (v.getId() == null) {
em.persist(v);
return v;
}
return em.merge(v);
}
}
Note
Ещё 5-7 лет назад DAO было принято делать @Stateless EJB ради автоматических транзакций. Сейчас, когда есть
@Transactional для CDI, чаще выбирают CDI + @Transactional на сервисе.
filter - сквозная инфраструктура: логирование, аутентификация и т.д.
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthFilter implements ContainerRequestFilter {
@Inject
AuthService auth; // CDI
@Override
public void filter(ContainerRequestContext ctx) {
// проверить токен
}
}
Итого: CDI - по умолчанию для всего, что внедряется: контроллеры, сервисы, DAO, фильтры, hub'ы. EJB - только ради их специфики. DTO, сущности и утилиты - не бины. Так получается CDI-центричная архитектура Jakarta EE: EJB по необходимости, а не по привычке.