运行 jsf 表单提交的后台任务
Running a task in background on jsf form submit
在我的 JSF 应用程序中,我有一个过程需要很长时间才能完成,我不希望用户一直等到它完成。我正在尝试在后台实现某种 'fire and forget task' 到 运行。
我使用的是 @Asynchronous
方法。这是正确的做法吗?
我的控制器:
@ViewScoped
@Named
public class Controller implements Serializable {
private static final long serialVersionUID = -6252722069169270081L;
@Inject
private Record record;
@Inject
private Service service;
public void save() {
this.record.generateHash();
boolean alreadyExists = this.service.existsBy(this.record.getHash());
if (alreadyExists)
Messages.add(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", "This record already exists"));
else {
this.service.save(this.record);
this.clearFields();
}
}
}
我的服务:
@Stateless
public class Service extends AbstractService<Record> {
private static final long serialVersionUID = -6327726420832825798L;
@Inject
private BeanManager beanManager;
@Override
public void save(Record record) {
super.save(record);
this.preProcess(record);
}
@Asynchronous
private void preProcess(Cd cd) {
// Long task running here ...
this.beanManager.fireEvent(cd);
}
}
但即使使用这种方法,用户仍然停留在页面上,直到 preProcess
方法完成。
这里的问题是,修改 EJB(和 CDI bean)行为的注释仅在被注入到适当注入点的“代理”对象调用时应用,例如用 @EJB
注释的字段或 @Inject
.
这是因为容器如何实现修改行为的功能。容器注入到 EJB 客户端(和 normal-scoped CDI bean)的对象实际上是一个 proxy,它知道如何调用目标 bean 的正确实例(例如正确的e @RequestScoped
bean 的实例)。代理还实现了额外的行为,如 @Transactional
或 @Asynchronous
。通过 this
调用方法绕过代理功能!因此,将这些注释放在 non-public 方法上实际上是 NO-OP!
non-exclusive 个解决方案列表:
将 preProcess()
移动到不同的 EJB,使其成为 public 并保留 @Asynchronous
注释
创建 preProcess()
public 并从 Controller
调用它
如果计算真正是 Service
私有的并且公开它会破坏设计,并且您不介意做更多的手动工作,您总是可以 运行 来自 container-provided ManagedExecutorService
:
的异步任务
@Resource
private ManagedExecutorService managedExecutorService;
注意执行代码的线程的语义 - 更具体地说,注意传播哪些上下文值,哪些不传播!那么,对于 @Asynchronous
方法,您也必须注意这一点!
在我的 JSF 应用程序中,我有一个过程需要很长时间才能完成,我不希望用户一直等到它完成。我正在尝试在后台实现某种 'fire and forget task' 到 运行。
我使用的是 @Asynchronous
方法。这是正确的做法吗?
我的控制器:
@ViewScoped
@Named
public class Controller implements Serializable {
private static final long serialVersionUID = -6252722069169270081L;
@Inject
private Record record;
@Inject
private Service service;
public void save() {
this.record.generateHash();
boolean alreadyExists = this.service.existsBy(this.record.getHash());
if (alreadyExists)
Messages.add(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", "This record already exists"));
else {
this.service.save(this.record);
this.clearFields();
}
}
}
我的服务:
@Stateless
public class Service extends AbstractService<Record> {
private static final long serialVersionUID = -6327726420832825798L;
@Inject
private BeanManager beanManager;
@Override
public void save(Record record) {
super.save(record);
this.preProcess(record);
}
@Asynchronous
private void preProcess(Cd cd) {
// Long task running here ...
this.beanManager.fireEvent(cd);
}
}
但即使使用这种方法,用户仍然停留在页面上,直到 preProcess
方法完成。
这里的问题是,修改 EJB(和 CDI bean)行为的注释仅在被注入到适当注入点的“代理”对象调用时应用,例如用 @EJB
注释的字段或 @Inject
.
这是因为容器如何实现修改行为的功能。容器注入到 EJB 客户端(和 normal-scoped CDI bean)的对象实际上是一个 proxy,它知道如何调用目标 bean 的正确实例(例如正确的e @RequestScoped
bean 的实例)。代理还实现了额外的行为,如 @Transactional
或 @Asynchronous
。通过 this
调用方法绕过代理功能!因此,将这些注释放在 non-public 方法上实际上是 NO-OP!
non-exclusive 个解决方案列表:
将
preProcess()
移动到不同的 EJB,使其成为 public 并保留@Asynchronous
注释创建
调用它preProcess()
public 并从Controller
如果计算真正是
的异步任务Service
私有的并且公开它会破坏设计,并且您不介意做更多的手动工作,您总是可以 运行 来自 container-providedManagedExecutorService
:@Resource private ManagedExecutorService managedExecutorService;
注意执行代码的线程的语义 - 更具体地说,注意传播哪些上下文值,哪些不传播!那么,对于
@Asynchronous
方法,您也必须注意这一点!