如何从请求中放入自定义 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)

我觉得这道题由几个部分组成:

  1. 如何将值注入批处理作业?
  2. 如何将基于上下文的值播种到批处理作业?
  3. 如何在批处理作业中进入RequestScope?
  4. 如何创建自定义作用域?
  5. 如何进入自定义范围?
  6. 如何在自定义范围内播种值?

我会尽量回答所有个别问题,但请记住,我最近才开始使用 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 toMETA-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;
}