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

标签 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...
    }
}

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

在进程尝试使用 processHelper 之前一切正常,它运行大量 Hibernate 查询以继续进程的持久性部分。当调用 processHelper 的第一个方法时,出现以下异常:

WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped

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

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

最佳答案

异常表明 ProcessHelper@RequestScoped

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

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

您提到 ProcessHelper 反过来运行一些数据库查询。这意味着它应该在事务中运行。在那种情况下,您应该使它成为完全有值(value)的 EJB 而不是 CDI 托管 bean。因此,也将 ProcessHelper 设为 @Stateless,或者将所有 DB 交互作业移动到 ProcessManager EJB 中。这也是可能的。

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

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

@Named
@RequestScoped
public class ProcessBacking {

    @Inject
    private ProcessManager processManager;

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

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

    // ...
}

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

}

@Stateless
public class ProcessHelper {

    @PersistenceContext
    private EntityManager entityManager;

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

}

请注意,@Singleton 默认情况下是读/写锁定的。因此,在 start() 完成之前,您不能调用 getResult()。因此 ConcurrencyManagementType.BEAN,这意味着它是未锁定的,因此基本上调用者自己负责并发管理。只要该进程仍在运行,您就可以继续刷新页面。

另见:

关于jsf - 如何在@Asynchronous 方法中正确使用CDI?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45545743/

相关文章:

jsf - p :schedule not rendering when its slotLabelFormat attribute is specified

java - 在 Flyway 上显示进度

javascript - 异步加载 AngularJS 应用程序

c# - 如何将异步与锁定结合起来?

json - 如何使用 mongoose 和异步 waterfall 模型在 MongoDb 中存储数据

java - JPA 1.0 和 Hibernate 3.4 在锁定时生成 FOR UPDATE NOWAIT

jsf - 使用 ui 的复合组件调用方法中缺少参数值 :repeat

maven - 无法从jsf页面获取值

javascript - 我可以从 javascript 调用 ejb 吗?

java - 正确的 EJB 异常处理 - 来自客户端的 ClassNotFoundException