Бины и внедрение
CDI (Contexts and Dependency Injection) — спецификация, которая создаёт объекты приложения и связывает их друг с другом. Объект, которым управляет CDI, называется бином. Вместо new PostStore() класс просто объявляет, что ему нужно хранилище, а контейнер CDI сам создаёт его и внедряет (inject) в поле. Так все части приложения получают один и тот же объект, а код не зависит от того, как этот объект собран.
Какой класс становится бином
В Jakarta EE бином становится класс с аннотацией области видимости: @ApplicationScoped, @RequestScoped, @SessionScoped, @Dependent. Хранилище из прошлой главы уже бин:
// src/main/java/com/example/blog/PostStore.java (фрагмент)
@ApplicationScoped
public class PostStore {
// ...
}Файл beans.xml для этого не нужен: сервер сам находит классы с такими аннотациями. Ресурсы Jakarta REST, сервлеты и фильтры тоже управляются контейнером, поэтому в них работает внедрение.
Внедрение в поле
Ресурс получает хранилище аннотацией @Inject:
// src/main/java/com/example/blog/PostResource.java (фрагмент)
@Inject
PostStore store;То же самое работает в сервлете. Создайте src/main/java/com/example/blog/StatsServlet.java:
// src/main/java/com/example/blog/StatsServlet.java
package com.example.blog;
import jakarta.inject.Inject;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/stats")
public class StatsServlet extends HttpServlet {
@Inject
PostStore store;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.setContentType("text/plain; charset=UTF-8");
response.getWriter().println("Записей в блоге: " + store.findAll().size());
}
}Сервлет и REST-ресурс получили один и тот же объект хранилища. Это легко проверить: добавим запись через API и посмотрим счётчик.
curl http://localhost:8080/blog/stats
curl -H "Content-Type: application/json" -d '{"title":"CDI","body":"Бины и внедрение."}' http://localhost:8080/blog/api/posts
curl http://localhost:8080/blog/statsЗаписей в блоге: 2
{"body":"Бины и внедрение.","createdAt":"2026-10-04","id":3,"title":"CDI"}
Записей в блоге: 3
Внедрение через конструктор
Бины могут зависеть друг от друга. Сервис публикации получает хранилище через конструктор:
// src/main/java/com/example/blog/PostService.java
package com.example.blog;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class PostService {
private PostStore store;
protected PostService() {
// нужен контейнеру для создания прокси
}
@Inject
public PostService(PostStore store) {
this.store = store;
}
public Post publish(NewPost data) {
return store.add(data);
}
}Для бинов @ApplicationScoped контейнер создаёт прокси — объект-посредник, который передаёт вызовы настоящему бину. Прокси требует конструктор без параметров, поэтому рядом с @Inject-конструктором нужен protected PostService(). Без него приложение не запустится с ошибкой WELD-001410: The injection point has non-proxyable dependencies. В ресурсе замените store.add(data) на вызов сервиса:
// src/main/java/com/example/blog/PostResource.java (фрагмент)
@Inject
PostService service;
// в методе create:
Post post = service.publish(data);Способы внедрения
| Способ | Пример | Когда удобен |
|---|---|---|
| Поле | @Inject PostStore store; | Ресурсы, сервлеты, короткий код |
| Конструктор | @Inject PostService(PostStore store) | Зависимости видны сразу, удобно тестировать |
| Метод | @Inject void setStore(PostStore store) | Редко |
Частая ошибка
Забыть аннотацию области видимости. Если убрать @ApplicationScoped у PostStore, класс перестаёт быть бином, и приложение не запустится. В логе сервера появится:
WELD-001408: Unsatisfied dependencies for type PostStore with qualifiers @Default
at injection point [BackedAnnotatedField] @Inject com.example.blog.PostResource.store
«Unsatisfied dependencies» значит, что для поля с @Inject контейнер не нашёл ни одного подходящего бина.