Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Продюсеры и квалификаторы

Аннотацию области видимости можно поставить только на свой класс. А как внедрить 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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.