>简而言之,我想从 rest 请求中放入配置类的自定义范围特定实例。主要问题是自定义作用域(来自 JBeret https://jberet.gitbooks.io/jberet-user-guide/content/custom_cdi_scopes/index.html 的 JobScoped)在作业启动后符合条件。我知道在启动作业时可以添加属性,但我的配置类聚合了很多配置,而且非常复杂因此,将该文件转换为属性类会非常不舒服。
详情如下:
这是 rest 请求伪代码:
@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);
}
我想实现的是在 Job 上下文中注入一些配置文件,如下所示:
public class MyReader implements ItemReader {
@Inject
private Configuration configFile;
}
配置类如下所示:
@JobScoped
public class Configuration {
// some flags, methods etc
}
我已经阅读了有关实例,提供程序的信息,但不知道如何在我的情况下使用它们。 事实上,我认为不可能使用它们,因为这些工作是由它们的名称标识的,这是动态的 并在运行时已知。
同时,我
发现了与我类似的情况:我是否可以创建一个请求范围的对象并从任何位置访问它,并避免在 JAX-RS 中将其作为参数传递?
但随后会出现缺少上下文的问题。当作业启动时,有 JobScoped 上下文。根据上述解决方案,我将配置注释为请求范围,然后我收到:
org.jboss.weld.context.ContextNotActiveException: WELD-001303: No 作用域类型 javax.enterprise.context.RequestScoped 的活动上下文 在 org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:689) 在 org.jboss.weld.bean.ContextualInstanceStrategy$DefaultContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:90) 在 org.jboss.weld.bean.ContextualInstanceStrategy$CachingContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:165) 在 org.jboss.weld.bean.ContextualInstance.getIfExists(ContextualInstance.java:63) 在 org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:83) 在 org.jboss.weld.bean.proxy.ProxyMethodHandler.getInstance(ProxyMethodHandler.java:125) 配置$Proxy$_$$_WeldClientProxy.toString(未知来源)
我认为这个问题由几个部分组成:
- 如何将值注入到批处理作业中?
- 如何将基于上下文的值播种到批处理作业?
- 如何在批处理作业中输入请求范围?
- 如何创建自定义范围?
- 如何输入自定义范围?
- 如何在自定义范围内设定值种子?
我将尝试回答所有单独的问题,但请记住,我最近才开始使用CDI/Weld,并且没有JBeret的经验。
1. 如何将值注入到批处理作业中?
我添加这个问题的原因是,我认为Configuration
可能不需要是一个作用域实体。如果Configuration
没有特定于范围的内容,则也可以是@Singleton
或@Stateless
。例如,从配置文件、资源或环境变量中考虑,它们在运行时不会更改。非作用域(或单例作用域)依赖项可以使用常规@Inject
字段很好地注入到 batchlet 中,而无需任何@JobScoped
注释。
2. 如何将基于上下文的值种子到批处理作业?
那么,如果实际值取决于上下文并且不能以@Singleton
方式注入怎么办?根据 JBeret 文档,最好通过 Properties
传递所有配置。然后可以从JobContext
中读取这些注释,或者使用@BatchProperty
注释注入。这仅适用于可从字符串序列化的预定义类型列表。
@Named
public class MyBatchlet extends AbstractBatchlet {
@Inject
@BatchProperty(name = "number")
int number;
}
3. 如何在批处理作业中输入@RequestScope
?
我认为你不应该。@RequestScope
仅用于请求。如果依赖项依赖于应在请求外部可访问的@RequestScope
,请考虑引入自定义作用域。
如果您确实需要以编程方式输入
@RequestScope
,您可以为其定义自己的上下文并输入该上下文(请参阅下面的第 4 部分)或默认输入上下文,如 Dan Haywood 在这篇博文中所述,他试图进入 Java SE 中的@RequestScope
。
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));
}
}
请注意,我们使用的是 META-INF/services/javax.enterprise.inject.spi.Extension'ScopeContext from microscoped here. Furthermore, you should register your extension by adding the full classname to
。
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 范围正确种子的问题:焊接 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
是更好的选择。
我要再次感谢Jan-Willem Gmelig Meyling,因为你的回答非常有帮助。无论如何,我想使用给定的作用域 JBeret 它是 JobScoped,今天它只能在 TYPE 级别使用。我按照Jan-Willem Gmelig Meyling的建议做了类似的解决方法,但是:
- 可以使用 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;
}