Продюсеры и квалификаторы
Аннотацию области видимости можно поставить только на свой класс. А как внедрить java.time.Clock или строку с названием сайта? Для этого есть продюсер (producer) — метод, помеченный @Produces, который создаёт объект для внедрения. Контейнер вызывает его каждый раз, когда кому-то нужен объект этого типа.
Продюсер часов
Создайте класс src/main/java/com/example/blog/BlogProducers.java:
// src/main/java/com/example/blog/BlogProducers.java
package com.example.blog;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import java.time.Clock;
@ApplicationScoped
public class BlogProducers {
@Produces
Clock clock() {
return Clock.systemDefaultZone();
}
}Обратите внимание на импорт: нужен jakarta.enterprise.inject.Produces, а не jakarta.ws.rs.Produces из Jakarta REST. Теперь хранилище может получать текущую дату из внедрённых часов:
// src/main/java/com/example/blog/PostStore.java (фрагмент)
@Inject
Clock clock;
public Post add(NewPost data) {
long id = nextId.getAndIncrement();
Post post = new Post(id, data.title(), data.body(), LocalDate.now(clock));
posts.put(id, post);
return post;
}Зачем это нужно: в тесте можно подставить Clock.fixed(...) и всегда получать одну и ту же дату.
Квалификатор
Строк в приложении много, и контейнеру нужно понять, какую именно внедрить. Квалификатор — своя аннотация, которая помечает и продюсер, и место внедрения:
// src/main/java/com/example/blog/SiteName.java
package com.example.blog;
import jakarta.inject.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface SiteName {
}Добавьте в BlogProducers второй продюсер:
// src/main/java/com/example/blog/BlogProducers.java (фрагмент)
@Produces
@SiteName
String siteName() {
return "Блог Анны";
}Внедрение
// src/main/java/com/example/blog/AboutResource.java
package com.example.blog;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.time.Clock;
import java.time.LocalTime;
@Path("/about")
@Produces(MediaType.TEXT_PLAIN)
public class AboutResource {
@Inject
@SiteName
String siteName;
@Inject
Clock clock;
@GET
public String about() {
return siteName + ", время на сервере " + LocalTime.now(clock).withNano(0);
}
}curl http://localhost:8080/blog/api/aboutБлог Анны, время на сервере 12:04:30
Что ещё умеют продюсеры
| Возможность | Пример |
|---|---|
| Область видимости результата | @Produces @ApplicationScoped — объект создаётся один раз |
| Поле-продюсер | @Produces @SiteName String siteName = "Блог Анны"; |
| Параметры | @Produces Report report(Clock clock) — контейнер внедрит clock |
| Освобождение | Метод с @Disposes закрывает созданный ресурс |
Без аннотации области видимости продюсер работает как @Dependent: новый объект для каждого места внедрения.
Частая ошибка
Забыть квалификатор в месте внедрения. Если написать просто @Inject String siteName;, контейнер ищет строку без квалификатора, не находит её, и приложение не запускается:
WELD-001408: Unsatisfied dependencies for type String with qualifiers @Default
at injection point [BackedAnnotatedField] @Inject com.example.blog.AboutResource.siteName
WELD-001475: The following beans match by type, but none have matching qualifiers:
- Producer Method [String] with qualifiers [@SiteName @Any] declared as [[BackedAnnotatedMethod] @Produces @SiteName com.example.blog.BlogProducers.siteName()],
Сообщение подсказывает решение: подходящий по типу бин есть, но с квалификатором @SiteName.