如何在@Asynchronous 方法中正确使用CDI?
How to correctly use CDI in @Asynchronous method?
我有一个 JSF 2 应用程序(运行 在 JBoss AS 7.1 之上)当用户单击页面中的按钮时必须启动一个漫长的过程。这个想法是为了有一个不阻塞的交互,所以用户可以等待并查看结果,或者只是关闭页面并稍后返回查看它是如何进行的或结果,如果该过程已经结束。
流程本身编码如下(简化)class:
@Stateless
@LocalBean
@ApplicationScoped
public class MyProcessManager {
@Inject
private ProcessHelper processHelper;
@Asynchronous
public void start(final ProcessParameters parameters) {
// the process...
}
}
这样一个class被标记为@ApplicationScoped
,因为所有进程运行(对于所有用户)都由它保留。因此,当单击按钮时,支持 bean 设置一些参数并调用异步方法 start()
.
在进程尝试使用 processHelper
之前一切正常,它运行大量 Hibernate 查询以继续进程的持久性部分。当调用 processHelper
的第一个方法时,我得到以下异常:
WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped
作为附加信息,永远不会命中此类方法内的断点。
发生了什么以及如何解决它?
我已经为此搜索了解决方案,但找不到明确的答案。但它可能对你有用。
显然,CDI 不会使用异步方法传播它的范围(实际上它不会将上下文传播到任何其他线程),因此,当您尝试 @Inject
资源时,您会收到一个错误。我找不到有关此行为的适当文档,但我遇到了至少几个与您的问题非常相似的问题:
Java injection inside @Asynchronous bean
Communicating between cdi session contexts — with database, will proper cdi context be invoked?
如果我们假设这实际上是 CDI 的当前行为,那么解决此问题的最佳方法是删除 @Inject
并创建 ProcessHelper
的新实例,或者也许去掉 @Asynchronous
注释并使用 ManagedExecutorService
,就像我在上面链接的第一个问题中所建议的那样。
异常表明 ProcessHelper
是 @RequestScoped
。
当调用 @Asynchronous
时,会生成一个全新的独立线程,该线程 而非 由 HTTP servlet 容器控制。在该线程的上下文中,因此无法在任何地方进行 HTTP 请求或 HTTP 会话。只能用@ApplicationScoped
,不能用@RequestScoped
,更不用说@SessionScoped
.
至于 ProcessManager
本身,组合 @Stateless @ApplicationScoped
没有意义。您很可能实际上想要 @javax.ejb.Singleton
。额外的好处是它是有状态的,因此您可以将过程结果作为实例变量保存在那里。
您提到 ProcessHelper
反过来 运行 一些数据库查询。这意味着它应该 运行 在交易中。在那种情况下,您应该使它成为完全有价值的 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
默认情况下 read/write 已锁定。因此,在 start()
完成之前,您不能调用 getResult()
。因此 ConcurrencyManagementType.BEAN
,这意味着它已解锁,因此本质上调用者本身负责并发管理。这允许您在进程仍在 运行s 时继续刷新页面。
另请参阅:
- Is it safe to start a new thread in a JSF managed bean?
我有一个 JSF 2 应用程序(运行 在 JBoss AS 7.1 之上)当用户单击页面中的按钮时必须启动一个漫长的过程。这个想法是为了有一个不阻塞的交互,所以用户可以等待并查看结果,或者只是关闭页面并稍后返回查看它是如何进行的或结果,如果该过程已经结束。
流程本身编码如下(简化)class:
@Stateless
@LocalBean
@ApplicationScoped
public class MyProcessManager {
@Inject
private ProcessHelper processHelper;
@Asynchronous
public void start(final ProcessParameters parameters) {
// the process...
}
}
这样一个class被标记为@ApplicationScoped
,因为所有进程运行(对于所有用户)都由它保留。因此,当单击按钮时,支持 bean 设置一些参数并调用异步方法 start()
.
在进程尝试使用 processHelper
之前一切正常,它运行大量 Hibernate 查询以继续进程的持久性部分。当调用 processHelper
的第一个方法时,我得到以下异常:
WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped
作为附加信息,永远不会命中此类方法内的断点。
发生了什么以及如何解决它?
我已经为此搜索了解决方案,但找不到明确的答案。但它可能对你有用。
显然,CDI 不会使用异步方法传播它的范围(实际上它不会将上下文传播到任何其他线程),因此,当您尝试 @Inject
资源时,您会收到一个错误。我找不到有关此行为的适当文档,但我遇到了至少几个与您的问题非常相似的问题:
Java injection inside @Asynchronous bean
Communicating between cdi session contexts — with database, will proper cdi context be invoked?
如果我们假设这实际上是 CDI 的当前行为,那么解决此问题的最佳方法是删除 @Inject
并创建 ProcessHelper
的新实例,或者也许去掉 @Asynchronous
注释并使用 ManagedExecutorService
,就像我在上面链接的第一个问题中所建议的那样。
异常表明 ProcessHelper
是 @RequestScoped
。
当调用 @Asynchronous
时,会生成一个全新的独立线程,该线程 而非 由 HTTP servlet 容器控制。在该线程的上下文中,因此无法在任何地方进行 HTTP 请求或 HTTP 会话。只能用@ApplicationScoped
,不能用@RequestScoped
,更不用说@SessionScoped
.
至于 ProcessManager
本身,组合 @Stateless @ApplicationScoped
没有意义。您很可能实际上想要 @javax.ejb.Singleton
。额外的好处是它是有状态的,因此您可以将过程结果作为实例变量保存在那里。
您提到 ProcessHelper
反过来 运行 一些数据库查询。这意味着它应该 运行 在交易中。在那种情况下,您应该使它成为完全有价值的 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
默认情况下 read/write 已锁定。因此,在 start()
完成之前,您不能调用 getResult()
。因此 ConcurrencyManagementType.BEAN
,这意味着它已解锁,因此本质上调用者本身负责并发管理。这允许您在进程仍在 运行s 时继续刷新页面。
另请参阅:
- Is it safe to start a new thread in a JSF managed bean?