我有一个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
作为附加信息,这种方法内部的断点永远不会被击中。
发生了什么以及如何解决?
答案 0 :(得分:2)
该例外表明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
默认为读/写锁定。因此,在getResult()
完成之前,您无法调用start()
。因此ConcurrencyManagementType.BEAN
,这意味着它被解锁,因此基本上调用者本身负责并发管理。这样,只要进程仍在运行,您就可以不断刷新页面。
答案 1 :(得分:0)
我已经为此寻找解决方案,但无法找到明确的答案。但它可能对你有用。
显然CDI不会使用异步方法传播它的范围(实际上它不会将上下文传播到任何其他线程),因此当您尝试@Inject
资源时会收到错误。我无法找到适合这种行为的文档,但我至少遇到过几个与你的问题非常类似的问题:
Java injection inside @Asynchronous bean
Communicating between cdi session contexts — with database, will proper cdi context be invoked?
如果我们假设这实际上是CDI的当前行为,那么解决此问题的最佳方法是摆脱ProcessHelper
并创建@Asynchronous
的新实例,或者可能获得删除ManagedExecutorService
注释并使用{{1}},就像我在上面链接的第一个问题中所建议的那样。