Как поместить в настраиваемую область / контекст (JobScoped - настраиваемую область CDI) конкретный экземпляр из запроса, чтобы сделать его инъекционным?

Говоря в двух словах, я бы хотел поместить в настраиваемую область конкретный экземпляр класса Configuration из запроса rest. Основная проблема заключается в том, что настраиваемая область (JobScoped из JBeret https://jberet.gitbooks.io/jberet-user-guide/content/custom_cdi_scopes/index.html) пригодна после запуска задания. Я знаю, что есть возможность добавлять свойства при запуске задания, но мой класс Configuration объединяет множество конфигураций, и это довольно сложно, поэтому было бы очень неудобно конвертировать эти файлы в класс Properties.

Подробности ниже:

Это псевдокод запроса на отдых:

@Path("/job")
public class RunJob {

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("/start")
public String startJob(@FormDataParam("file") InputStream uploadedInputStream) {
    JobOperatorImpl jobOperator = (JobOperatorImpl) BatchRuntime.getJobOperator();

    Configuration config = new Configuration(uploadedInputStream);
    Properties properties = new Properties();
    jobOperator.start(job, properties);
}

Чего я хотел добиться, так это внедрить некоторые файлы конфигурации в контексте задания, как показано ниже:

public class MyReader implements ItemReader {

@Inject
private Configuration configFile;
}

Класс конфигурации представлен как ниже:

@JobScoped
public class Configuration {
 // some flags, methods etc
}

Я читал об Instance, Provider, но не знаю, как использовать их в моем случае. На самом деле я думаю, что их невозможно использовать, потому что задания идентифицируются по их имени, которое является динамическим и известным во время выполнения.


Между тем я обнаружил, что у меня похожая ситуация: могу ли я создать объект в области запроса и получить к нему доступ из любого места и избежать передачи его в качестве параметра в JAX-RS?

Но тогда возникает проблема с отсутствующим контекстом. Когда Job запускается, существует контекст JobScoped. В соответствии с вышеупомянутым решением, я аннотировал конфигурацию как RequestScoped, затем я получил:

org.jboss.weld.context.ContextNotActiveException: WELD-001303: нет активных контекстов для типа области видимости javax.enterprise.context.RequestScoped в org.jboss.weld.manager.BeanManagerImpl.get.text(BeanManagerImpl) 9, (ContextualInstance.java:63) в org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:83) в org.jboss.weld.bean.proxy.ProxyMethodHandler.get.stj (ProxyMethodH) $Proxy$_$$_WeldClientProxy.toString(неизвестный источник)

3 ответа

Решение

Я думаю, что этот вопрос состоит из нескольких частей:

  1. Как ввести значения в пакетные задания?
  2. Как заполнить контекстные значения для пакетных заданий?
  3. Как ввести RequestScope в пакетное задание?
  4. Как создать собственную область?
  5. Как войти в пользовательскую область?
  6. Как заполнить значение в пользовательской области?

Я постараюсь ответить на все отдельные вопросы, но имейте в виду, что я только недавно начал использовать CDI/Weld и не имею опыта работы с JBeret.

1. Как ввести значения в пакетные задания?

Я добавляю этот вопрос, потому что думаю, Configuration возможно, не нужно быть объектом с ограниченным доступом. Если Configuration не имеет ничего конкретного для сферы применения, это может быть @Singleton или же @Stateless также. Подумайте, например, из файлов конфигурации, ресурсов или переменных среды, которые не изменятся во время выполнения. Нераспределенные (или синглтон-зависимые) зависимости можно просто впрыскивать в пакеты, используя обычные @Inject поля, без необходимости @JobScoped аннотаций.

2. Как заполнить контекстные значения для пакетных заданий?

Так что, если фактическое значение зависит от контекста и не может быть введено в @Singleton мода? Исходя из документации JBeret, предпочтительно передать всю конфигурацию Properties, Их можно прочитать из JobContext или вводится с помощью @BatchProperty аннотаций. Это работает только для предварительно определенного списка типов, которые сериализуются из String.

@Named
public class MyBatchlet extends AbstractBatchlet {

    @Inject
    @BatchProperty(name = "number")
    int number;

}

3. Как войти в @RequestScope в пакетной работе?

Я думаю, что вы не должны. @RequestScope только для запросов. Если у вас есть зависимости, зависящие от @RequestScope это должно быть доступно за пределами запроса, рассмотрите возможность введения настраиваемой области.

Если вам действительно нужно ввести @RequestScope программно, вы можете определить свой собственный контекст для него и ввести этот контекст (см. часть 4 ниже) или ввести контекст по умолчанию, как указано в этом посте Дэна Хейвуда, в его попытке попасть в @RequestScope в Java SE.

4. Как создать собственную область?

Довольно легко создать пользовательскую область видимости. Однако для пользовательской области требуется реализация контекста области. Я обнаружил, что это немного неясно в документации. К счастью, есть библиотека с микроскопом. Для этого примера вам нужно только microscoped-core зависимость, которая обеспечивает ScopeContext реализация, которая используется в их пользовательских областях. Мы будем использовать это ScopeContext для нашего простого объема, а также.

Сначала мы должны создать аннотацию Scope:

@Documented
@Scope
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface CustomScoped {}

Во-вторых, мы должны создать расширение:

public class CustomScopedExtension implements Extension, Serializable {

    public void addScope(@Observes final BeforeBeanDiscovery event) {
        event.addScope(CustomScoped, true, false);
    }

    public void registerContext(@Observes final AfterBeanDiscovery event) {
        event.addContext(new ScopeContext<>(CustomScoped.class));
    }

}

Обратите внимание, что мы используем ScopeContext from microscoped here. Furthermore, you should register your extension by adding the full classname to META-INF/ услуги / javax.enterprise.inject.spi.Extension`.

5. Как войти в пользовательскую область?

Теперь нам нужно войти в нашу сферу. Мы можем сделать это с помощью небольшого кода, который вы можете разместить, например, в сети Filter или метод перехватчик. Код использует BeanManager экземпляр, который можно получить с помощью @Inject:

ScopeContext<?> context = (ScopeContext<?>) beanManager.getContext(CustomScoped.class);
context.enter(key);
try {
     // continue computation
} finally {
    context.destroy(key);
}

6. Как заполнить значение в пользовательской области?

Я задавал себе тот же вопрос, и это решение я придумал. См. Также мой вопрос о том, как правильно выполнять заполнение из пользовательских областей Weld CDI: значение заполнения в пользовательской области Weld CDI. У меня есть решение для вашей проблемы, хотя:

@Singleton
public class ConfigurationProducer {

    private final InheritableThreadLocal<Configuration>  threadLocalConfiguration =
    new InheritableThreadLocal<>();

    @Produces
    @ActiveDataSet
    public ConfigurationConfiguration() {
       return threadLocalConfiguration.get()
    }

    public void setConfiguration(Configuration configuration) {
         threadLocalConfiguration.set(configuration);
    }    

}

Теперь из вашего перехватчика, написанного выше, вы можете залить ConfigurationProducer и использовать ConfigurationProducer #setConfiguration(Configuration) установить Configuration для текущей темы. Я все еще ищу лучшие варианты здесь.

Спецификация пакета (JSR 352) определяет стандартный способ передачи пользовательского объекта в задании путем вызова:

javax.batch.runtime.context.JobContext#setTransientUserData(myObject);

Для простых случаев этого должно хватить. Вы можете определить работу слушателя, ввести JobContext в свой класс слушателя работы, и внутри его startJob() метод, установить временные данные пользователя. Затем он будет доступен для всего выполнения задания и может быть привязан к другим значениям. Для более сложных случаев использования, @JobScoped это лучший выбор.

Во-первых, я хотел бы еще раз поблагодарить вас, Ян-Виллем Гмелиг Мейлинг, потому что ваш ответ был очень полезным. В любом случае, я хотел использовать данную область видимости от JBeret, которая называется JobScoped, сегодня она может использоваться только на уровне TYPE. Я сделал подобный обходной путь, как предложил Ян-Виллем Гмелиг Мейлинг, но:

  • можно использовать JobScoped
  • не нужно импортировать дополнительные библиотеки, все работает в CDI

Решение:

1) Класс конфигурации:

@JobScoped
public class Configuration
{...}

2) У JobListener магия случается. Дополнительные комментарии излишни.

Давайте мой код говорит сам за себя;)

import javax.batch.api.listener.AbstractJobListener;

public class MyJobListener extends AbstractJobListener{

    @Inject
    private Configuration jobScopedConfiguration;


    @Override
    public void beforeJob() throws Exception {
        enrichJobScopedConfigurationWithRequestConfiguration();
        ...
        super.beforeJob();
    }

    private void enrichJobScopedConfigurationWithRequestConfiguration(){
    Configuration requestConfiguration =
                (Configuration) BatchRuntime.getJobOperator().getJobExecution(currentExecutionId).getJobParameters()
                        .get("configuration");
        jobScopedConfiguration.updateWith(requestConfiguration);
    }

Теперь я могу ввести свою конфигурацию в любой пакетный артефакт jberet/java в контексте задания, например:

public class MyReader implements ItemReader {

@Inject
private Configuration configFile;
}
Другие вопросы по тегам