如何从请求中放入自定义 scope/context(JobScoped - 自定义 CDI 范围)特定实例以使其可注入?
How to put in custom scope/context (JobScoped - custom CDI scope) particular instance from request to make it injectable?
简而言之,我想在自定义范围内放置来自其余请求的配置 class 的特定实例。
主要问题是自定义范围(来自 JBeret https://jberet.gitbooks.io/jberet-user-guide/content/custom_cdi_scopes/index.html 的 JobScoped)在作业开始后是合格的。
我知道在开始工作时可以添加属性,但是我的 Configuration class 聚集了很多配置并且非常复杂
因此将此文件转换为 Properties class.
会非常不舒服
详情如下:
这是休息请求伪代码:
@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;
}
配置class如下所示:
@JobScoped
public class Configuration {
// some flags, methods etc
}
我读过有关 Instance、Provider 的信息,但不知道如何在我的案例中使用它们。
事实上,我认为使用它们是不可能的,因为这些工作是由动态的名称标识的
并且在运行时已知。
同时我发现了和我类似的情况:
Can I create a request-scoped object and access it from anywhere, and avoid passing it around as a parameter in JAX-RS?
但随后出现缺少上下文的问题。当 Job 开始时,有 JobScoped 上下文。
根据上面的解决方案,我将 Configuration 注释为 RequestScoped,然后我收到:
org.jboss.weld.context.ContextNotActiveException: WELD-001303: No
active contexts for scope type javax.enterprise.context.RequestScoped
at
org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:689)
at
org.jboss.weld.bean.ContextualInstanceStrategy$DefaultContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:90)
at
org.jboss.weld.bean.ContextualInstanceStrategy$CachingContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:165)
at
org.jboss.weld.bean.ContextualInstance.getIfExists(ContextualInstance.java:63)
at
org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:83)
at
org.jboss.weld.bean.proxy.ProxyMethodHandler.getInstance(ProxyMethodHandler.java:125)
Configuration$Proxy$_$$_WeldClientProxy.toString(Unknown Source)
我觉得这道题由几个部分组成:
- 如何将值注入批处理作业?
- 如何将基于上下文的值播种到批处理作业?
- 如何在批处理作业中进入RequestScope?
- 如何创建自定义作用域?
- 如何进入自定义范围?
- 如何在自定义范围内播种值?
我会尽量回答所有个别问题,但请记住,我最近才开始使用 CDI/Weld,并且没有使用 JBeret 的经验。
1。如何将值注入批处理作业?
我添加这个问题的原因是因为我认为 Configuration
可能不需要是范围实体。如果 Configuration
没有特定于范围,它也可以是 @Singleton
或 @Stateless
。例如,从不会在运行时更改的配置文件、资源或环境变量中考虑。可以使用常规 @Inject
字段将非作用域(或单例作用域)依赖项很好地注入 batchlet,而无需 @JobScoped
注释。
2。如何将基于上下文的值播种到批处理作业?
那么,如果实际值取决于上下文并且不能以 @Singleton
方式注入怎么办?基于 JBeret documentation,最好通过 Properties
传递所有配置。然后可以从 JobContext
读取这些信息,或使用 @BatchProperty
注释注入。这仅适用于可从字符串序列化的预定义类型列表。
@Named
public class MyBatchlet extends AbstractBatchlet {
@Inject
@BatchProperty(name = "number")
int number;
}
3。如何在批处理作业中输入@RequestScope
?
我认为你不应该。 @RequestScope
仅用于请求。如果您有依赖于 @RequestScope
的依赖项应该可以在请求之外访问,请考虑引入自定义范围。
If you really need to enter the @RequestScope
programatically, you can define your own context for it and enter that context (see part 4 below) or enter the context by default, as addressed in this blogpost by Dan Haywood, in his attempt to get into the @RequestScope
in Java SE.
4。如何创建自定义作用域?
创建自定义范围相当容易。然而,自定义范围需要范围上下文的实现。我发现这在文档中有点不清楚。幸好有图书馆 microscoped library。对于此示例,您只需要 microscoped-core
依赖项,它提供了在其自定义范围内使用的 ScopeContext
实现。我们也会将 ScopeContext
用于我们的简单范围。
首先我们必须创建范围注解:
@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/services/javax.enterprise.inject.spi.Extension`.
5。如何进入自定义作用域?
现在我们需要进入我们的范围。我们可以用一些代码来做到这一点,例如,您可以将这些代码放在 web Filter
或方法拦截器中。该代码使用了一个 BeanManager
实例,可以通过 @Inject
:
获得
ScopeContext<?> context = (ScopeContext<?>) beanManager.getContext(CustomScoped.class);
context.enter(key);
try {
// continue computation
} finally {
context.destroy(key);
}
6.如何在自定义范围内播种值?
我一直在问自己同样的问题,这就是我想出的解决方案。另请参阅我关于如何从自定义 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
注入您的作业侦听器 class,并在其 startJob()
方法中设置瞬态用户数据。然后它将可用于整个作业执行,并且可以保留为其他值。对于更复杂的用例,@JobScoped
是更好的选择。
首先,我想再次感谢您 Jan-Willem Gmelig Meyling,因为您的回答非常有帮助。
无论如何,我想使用 JBeret 给定的范围,即 JobScoped,今天它只能在 TYPE 级别上使用。
我按照 Jan-Willem Gmelig Meyling 的建议做了类似的解决方法,但是:
- 可以使用 JobScoped
- 不需要导入额外的库,一切都在 CDI 中工作
解法:
1)
配置class:
@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;
}
简而言之,我想在自定义范围内放置来自其余请求的配置 class 的特定实例。 主要问题是自定义范围(来自 JBeret https://jberet.gitbooks.io/jberet-user-guide/content/custom_cdi_scopes/index.html 的 JobScoped)在作业开始后是合格的。 我知道在开始工作时可以添加属性,但是我的 Configuration class 聚集了很多配置并且非常复杂 因此将此文件转换为 Properties class.
会非常不舒服详情如下:
这是休息请求伪代码:
@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;
}
配置class如下所示:
@JobScoped
public class Configuration {
// some flags, methods etc
}
我读过有关 Instance、Provider 的信息,但不知道如何在我的案例中使用它们。 事实上,我认为使用它们是不可能的,因为这些工作是由动态的名称标识的 并且在运行时已知。
同时我发现了和我类似的情况: Can I create a request-scoped object and access it from anywhere, and avoid passing it around as a parameter in JAX-RS?
但随后出现缺少上下文的问题。当 Job 开始时,有 JobScoped 上下文。 根据上面的解决方案,我将 Configuration 注释为 RequestScoped,然后我收到:
org.jboss.weld.context.ContextNotActiveException: WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped at org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:689) at org.jboss.weld.bean.ContextualInstanceStrategy$DefaultContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:90) at org.jboss.weld.bean.ContextualInstanceStrategy$CachingContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:165) at org.jboss.weld.bean.ContextualInstance.getIfExists(ContextualInstance.java:63) at org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:83) at org.jboss.weld.bean.proxy.ProxyMethodHandler.getInstance(ProxyMethodHandler.java:125) Configuration$Proxy$_$$_WeldClientProxy.toString(Unknown Source)
我觉得这道题由几个部分组成:
- 如何将值注入批处理作业?
- 如何将基于上下文的值播种到批处理作业?
- 如何在批处理作业中进入RequestScope?
- 如何创建自定义作用域?
- 如何进入自定义范围?
- 如何在自定义范围内播种值?
我会尽量回答所有个别问题,但请记住,我最近才开始使用 CDI/Weld,并且没有使用 JBeret 的经验。
1。如何将值注入批处理作业?
我添加这个问题的原因是因为我认为 Configuration
可能不需要是范围实体。如果 Configuration
没有特定于范围,它也可以是 @Singleton
或 @Stateless
。例如,从不会在运行时更改的配置文件、资源或环境变量中考虑。可以使用常规 @Inject
字段将非作用域(或单例作用域)依赖项很好地注入 batchlet,而无需 @JobScoped
注释。
2。如何将基于上下文的值播种到批处理作业?
那么,如果实际值取决于上下文并且不能以 @Singleton
方式注入怎么办?基于 JBeret documentation,最好通过 Properties
传递所有配置。然后可以从 JobContext
读取这些信息,或使用 @BatchProperty
注释注入。这仅适用于可从字符串序列化的预定义类型列表。
@Named
public class MyBatchlet extends AbstractBatchlet {
@Inject
@BatchProperty(name = "number")
int number;
}
3。如何在批处理作业中输入@RequestScope
?
我认为你不应该。 @RequestScope
仅用于请求。如果您有依赖于 @RequestScope
的依赖项应该可以在请求之外访问,请考虑引入自定义范围。
If you really need to enter the
@RequestScope
programatically, you can define your own context for it and enter that context (see part 4 below) or enter the context by default, as addressed in this blogpost by Dan Haywood, in his attempt to get into the@RequestScope
in Java SE.
4。如何创建自定义作用域?
创建自定义范围相当容易。然而,自定义范围需要范围上下文的实现。我发现这在文档中有点不清楚。幸好有图书馆 microscoped library。对于此示例,您只需要 microscoped-core
依赖项,它提供了在其自定义范围内使用的 ScopeContext
实现。我们也会将 ScopeContext
用于我们的简单范围。
首先我们必须创建范围注解:
@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/services/javax.enterprise.inject.spi.Extension`.
5。如何进入自定义作用域?
现在我们需要进入我们的范围。我们可以用一些代码来做到这一点,例如,您可以将这些代码放在 web Filter
或方法拦截器中。该代码使用了一个 BeanManager
实例,可以通过 @Inject
:
ScopeContext<?> context = (ScopeContext<?>) beanManager.getContext(CustomScoped.class);
context.enter(key);
try {
// continue computation
} finally {
context.destroy(key);
}
6.如何在自定义范围内播种值?
我一直在问自己同样的问题,这就是我想出的解决方案。另请参阅我关于如何从自定义 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
注入您的作业侦听器 class,并在其 startJob()
方法中设置瞬态用户数据。然后它将可用于整个作业执行,并且可以保留为其他值。对于更复杂的用例,@JobScoped
是更好的选择。
首先,我想再次感谢您 Jan-Willem Gmelig Meyling,因为您的回答非常有帮助。 无论如何,我想使用 JBeret 给定的范围,即 JobScoped,今天它只能在 TYPE 级别上使用。 我按照 Jan-Willem Gmelig Meyling 的建议做了类似的解决方法,但是:
- 可以使用 JobScoped
- 不需要导入额外的库,一切都在 CDI 中工作
解法:
1) 配置class:
@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;
}