如何在@Asynchronous方法中正确使用CDI?

Ale*_*xSC 4 jsf asynchronous ejb cdi

我有一个 JSF 2 应用程序(在 JBoss AS 7.1 之上运行),当用户单击页面中的按钮时,该应用程序必须启动一个很长的进程。其想法是进行非阻塞交互,因此用户可以等待并查看结果,或者简单地关闭页面并稍后返回以查看进展情况或结果(如果该过程已经结束)。

该过程本身在以下(简化的)类中进行编码:

@Stateless
@LocalBean
@ApplicationScoped
public class MyProcessManager {
    @Inject
    private ProcessHelper processHelper;

    @Asynchronous
    public void start(final ProcessParameters parameters) {
        // the process...
    }
}
Run Code Online (Sandbox Code Playgroud)

这样的类被标记为 ,@ApplicationScoped因为所有正在运行的进程(对于所有用户)都由它保存。因此,当单击按钮时,支持 bean 会设置一些参数并调用异步方法start()

一切都很顺利,直到进程尝试使用 时processHelper,它会运行许多 Hibernate 查询以继续进程的持久性部分。processHelper当调用第一个方法时,我得到以下异常:

WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped
Run Code Online (Sandbox Code Playgroud)

作为附加信息,此类方法内部的断点永远不会被命中。

发生了什么以及如何解决?

Bal*_*usC 5

例外表明ProcessHelper@RequestScoped

调用时@Asynchronous,会生成一个全新的独立线程,该线程不受HTTP servlet 容器控制。因此,在该线程的上下文中,任何地方都无法进行 HTTP 请求或 HTTP 会话。你只能用@ApplicationScoped,不能用@RequestScoped,更不用说@SessionScoped

就其ProcessManager本身而言,这种组合@Stateless @ApplicationScoped没有意义。您很可能实际上想要一个@javax.ejb.Singleton. 额外的好处是它是有状态的,因此您可以将过程结果作为实例变量保存在那里。

您提到这ProcessHelper又会运行一些数据库查询。这意味着它应该在事务中运行。在这种情况下,您应该将其设为完全值得的 EJB,而不是 CDI 托管 bean。因此,也创建ProcessHelper一个@Stateless,或者将所有数据库交互作业移至ProcessManagerEJB 中。这也是有可能的。

所以,总而言之,应该这样做:

<h:form>
    <h:commandButton value="Start" action="#{processBacking.start}" />
</h:form>
<p>
    Result (manually refresh page to check): #{processBacking.result}
</p>
Run Code Online (Sandbox Code Playgroud)

@Named
@RequestScoped
public class ProcessBacking {

    @Inject
    private ProcessManager processManager;

    public void start() {
        // ...
        processManager.start(parameters);
    }

    public ProcessResult getResult() {
        return processManager.getResult();
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class ProcessManager {

    private ProcessResult result;

    @Inject
    private ProcessHelper helper;

    @Asynchronous
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void start(ProcessParameters parameters) {
        ProcessResult result = runSomeLongRunningNonTransactionalProcess(parameters);
        this.result = helper.persist(result);
    }

    public ProcessResult getResult() {
        return result;
    }

}
Run Code Online (Sandbox Code Playgroud)

@Stateless
public class ProcessHelper {

    @PersistenceContext
    private EntityManager entityManager;

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public ProcessResult persist(ProcessResult result) {
        entityManager.persist(result);
        return result;
    }

}
Run Code Online (Sandbox Code Playgroud)

请注意,a@Singleton默认情况下是读/写锁定的。getResult()因此,在完成之前您无法调用start()。因此ConcurrencyManagementType.BEAN,这意味着它已解锁,因此本质上调用者本身负责并发管理。这允许您在进程仍在运行时不断刷新页面。

也可以看看: