我有一个 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/