我有一个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会话。只能使用@ApplicationScoped
,不能使用@RequestScoped
,更不能使用@SessionScoped
。
至于ProcessManager
本身,组合@Stateless @ApplicationScoped
没有意义。您很可能实际上想要一个@javax.ejb.Singleton
.额外的好处是它是有状态的,因此您可以将流程结果作为实例变量保留在那里。
您提到ProcessHelper
反过来运行一些数据库查询。这意味着它应该在事务中运行。在这种情况下,您应该使其成为一个完整的 EJB,而不是一个 CDI 管理的 Bean。因此,也ProcessHelper
@Stateless
,或者只是将所有数据库交互作业移动到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 管理的 Bean 中启动新线程是否安全?
我已经为此寻找解决方案,但找不到明确的答案。但它可能对您有用。
显然,CDI 不会使用异步方法传播其范围(实际上它不会将上下文传播到任何其他线程(,因此当您尝试@Inject
资源时,您会收到错误。我找不到此行为的适当文档,但我至少遇到了几个问题,其问题与您的问题非常相似:
@Asynchronous Bean 内部的 Java 注入
在 cdi 会话上下文之间进行通信 — 使用数据库时,是否会调用正确的 cdi 上下文?
如果我们假设这实际上是 CDI 的当前行为,那么解决此问题的最佳选择是摆脱@Inject
并创建一个新的ProcessHelper
实例,或者可能摆脱@Asynchronous
注释并使用ManagedExecutorService
,就像我上面链接的第一个问题中所建议的那样。