【问题标题】:How to put in custom scope/context (JobScoped - custom CDI scope) particular instance from request to make it injectable?如何从请求中放入自定义范围/上下文(JobScoped - 自定义 CDI 范围)特定实例以使其可注入?
【发布时间】:2016-11-18 15:01:04
【问题描述】:

简而言之,我想从休息请求中放入配置类的自定义范围特定实例。 主要问题是自定义范围(来自 JBeret https://jberet.gitbooks.io/jberet-user-guide/content/custom_cdi_scopes/index.html 的 JobScoped)在工作开始后是合格的。 我知道在开始工作时可以添加属性,但是我的 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);
}

我想要实现的是在 Job 的上下文中注入一些配置文件,如下所示:

public class MyReader implements ItemReader {

@Inject
private Configuration configFile;
}

配置类如下所示:

@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: 否 范围类型 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(Unknown Source)

【问题讨论】:

  • 我真的不知道批处理 api 是如何工作的,但你不能把你的配置放在你传递给操作员的属性中吗?属性可以包含字符串 (getProperty/setProperty) 或对象 (get/put)。
  • 感谢“或对象(获取/放置)”。这不是我想要的,但似乎别无选择。

标签: jakarta-ee jax-rs cdi java-batch jberet


【解决方案1】:

我认为这个问题由几个部分组成:

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

我将尝试回答所有个别问题,但请记住,我最近才开始使用 CDI/Weld,并且没有使用 JBeret 的经验。

1。如何将值注入批处理作业?

我之所以添加这个问题,是因为我认为Configuration 可能不需要成为范围实体。如果Configuration 没有特定于范围的内容,则它也可能是@Singleton@Stateless。例如,从不会在运行时更改的配置文件、资源或环境变量中考虑。可以使用常规的@Inject 字段将非范围(或单例范围)依赖项注入到批处理中,而无需任何@JobScoped 注释。

2。如何将基于上下文的值播种到批处理作业?

如果实际值取决于上下文并且不能以@Singleton 方式注入怎么办?基于 JBeret documentation,最好通过 Properties 传递所有配置。然后可以从JobContext 中读取这些信息,或者使用@BatchProperty 注释进行注入。这仅适用于可从字符串序列化的预定义类型列表。

@Named
public class MyBatchlet extends AbstractBatchlet {

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

}

3。如何在批处理作业中输入@RequestScope

我认为你不应该。 @RequestScope 仅用于请求。如果您有依赖于 @RequestScope 的依赖项,这些依赖项应该可以在请求之外访问,请考虑引入自定义范围。

如果您确实需要以编程方式输入@RequestScope,您可以为其定义自己的上下文并输入该上下文(参见下面的第 4 部分)或默认输入上下文,如 Dan Haywood 在this blogpost 中所述,他试图进入 Java SE 中的@RequestScope

4。如何创建自定义范围?

创建自定义范围相当容易。然而,自定义范围需要范围上下文的实现。我发现这在文档中有点不清楚。幸运的是有图书馆microscoped library。对于此示例,您只需要 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 toMETA-INF/services/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 范围正确播种的问题:Seed value in Weld CDI custom scope。不过,我确实有解决您的问题的方法:

@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。我仍在寻找更好的选择。

【讨论】:

  • 感谢您的回复。我想知道我是否可以将它调整到当前的自定义范围,即我在问题中提到的 JobScoped。然后我将从第 5 点开始,希望避免添加不仅依赖于我的第三方库;)而且可能会被拒绝......
  • 你也可以复制ScopeContext类,它是另一个依赖,但你只需要1或2个类。 JobScope 使用 JobScopeContext 实现。也许那个 Context 已经为您提供了完成这项工作的工具。请参阅:github.com/jberet/jsr352/blob/…。并请指出可能拒绝的同事讨论,喜欢看到最终结果:)
  • 我将您的答案标记为解决方案,因为它“对找到我的解决方案最有帮助”
  • 谢谢!你找到了什么解决方案?
【解决方案2】:

批处理规范 (JSR 352) 定义了一种在作业中传递用户对象的标准方法,方法是:

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

对于简单的情况,这应该足够了。您可以定义一个作业侦听器,将JobContext 注入您的作业侦听器类,并在其startJob() 方法中设置瞬态用户数据。然后它将可用于整个作业执行,并且可以保留为其他值。对于更复杂的用例,@JobScoped 是更好的选择。

【讨论】:

  • 您的想法与我的最终解决方案非常相似,但在工作开始期间我看不到 setTransientUserData 的可能性。当然,它可以像我一样作为 java.util.Properties 中的属性发送。获取配置也不是很方便,因为您需要注入 JobContext 然后从中获取 Object 并进行转换。另外,如果您想发送更多对象,则必须将其包装到某个容器中,因为 transientUserData 是一个默认对象。
  • 如果您将transientUserData 值设置为一个集合,您可以存储许多值,例如对象地图。
【解决方案3】:

首先,我要再次感谢 Jan-Willem Gmelig Meyling,因为您的回答非常有帮助。 无论如何,我想使用 JBeret 给定的范围,即 JobScoped,今天它只能用于 TYPE 级别。 我按照 Jan-Willem Gmelig Meyling 的建议做了类似的解决方法,但是:

  • 可以使用 JobScoped
  • 不必导入额外的库,一切都在 CDI 中运行

解决方案:

1) 配置类:

@JobScoped
public class Configuration
{...}

2) 在 JobListener 魔术发生了。额外的 cmets 是多余的。

让我的代码自己说话;)

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;
}

【讨论】:

    猜你喜欢
    • 2011-04-17
    • 1970-01-01
    • 1970-01-01
    • 2015-07-07
    • 2021-11-28
    • 1970-01-01
    • 2010-12-03
    • 1970-01-01
    • 2016-11-14
    相关资源
    最近更新 更多