Как поместить в настраиваемую область / контекст (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 ответа
Я думаю, что этот вопрос состоит из нескольких частей:
- Как ввести значения в пакетные задания?
- Как заполнить контекстные значения для пакетных заданий?
- Как ввести RequestScope в пакетное задание?
- Как создать собственную область?
- Как войти в пользовательскую область?
- Как заполнить значение в пользовательской области?
Я постараюсь ответить на все отдельные вопросы, но имейте в виду, что я только недавно начал использовать 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;
}