Java EE WebApp 中的长时间运行任务 +冰面

发布于 2024-12-20 23:41:36 字数 645 浏览 5 评论 0原文

我对 Java EE 的了解不多,但目前正在学习。

我提出了一个项目,其中涉及由用户调用的长时间运行的任务(长达几分钟)。该任务由几个步骤组成。当然我想向用户展示进度。

该项目使用 Java EE 以及 JPA、JSF 和 Icefaces。它在 Glassfish 上运行。

一位经验丰富的同事向我建议了以下模式:

  1. 创建一个无状态、异步 EJB,它创建响应对象并处理请求
  2. 在每个步骤后保留响应对象
  3. 在支持 bean 中,查询并显示响应对象

这效果很好。我唯一的问题是更新状态站点以反映进度。目前,我通过每 x 秒重新加载一次简单的 JavaScript 页面来完成此操作。

您知道一种方法/模式来反映从无状态 ejb 到 jsf 支持 bean 的当前步骤吗? 或者,我更喜欢这样,你知道一种方法来每 x 秒查询一个支持 bean 的值吗?

编辑:

我知道 Icefaces 推送机制,但我希望状态更新站点与计算 EJB 分离,原因如下:

  • 由于用户离开站点,支持 bean 可能已经被销毁并稍后返回以获取结果
  • 多个会话,因此一个用户可能存在多个 bean
  • 具有简洁的设计

I don't have much knowledge on Java EE but am currently learning it.

I've come up with a project which involves a long running task (up to several minutes) invoked by the user. The task consists of several steps. Of course I would like to show the progress to the user.

The project uses Java EE with JPA, JSF and Icefaces. It runs on Glassfish.

An experienced colleague adviced the following pattern to me:

  1. Create a stateless, asynchronous EJB which creates a response object and processes the request
  2. Persist the response object after each step
  3. In the backing bean, query and display the response object

This works well. My only problem is to update the status site to mirror the progress. Currently I am doing this with a simple JavaScript page reload every x seconds.

Do you know a way/pattern to reflect the current step from the stateless ejb to the jsf backing bean?
Or, and I would prefer that, do you know a way to query the value of a backing bean every x seconds?

Edit:

I am aware of the Icefaces push mechanism, but I want the status update site to be decoupled from the calculation EJB for the following reasons:

  • The backing bean might already be destroyed because the user left the site and return later to fetch the result
  • Multiple sessions and therefore multiple beans may exist for one user
  • Having a clean design

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

南冥有猫 2024-12-27 23:41:37

有多种选项可以传回此信息。如果 EJB 位于同一个 JVM 中,
您也可以使用一些单例 Map 并在某个键(会话 ID)下存储进度。

如果不是这种情况,您将需要一些共享状态或通信。有几个选项

  • 将其存储在可从两层访问的数据库上(sql、JNDI、LDAP - 更好的解决方案是键值存储,例如 redis - 如果你知道)
  • 使用一些消息传递将处理状态存储在 Web 层侧
  • 存储状态 上在 EJB 层一侧进行哈希处理,并提供另一种 SLSB 方法来检索此状态。

您的选择并不容易 - 所有这些解决方案都以不同的方式进行。

There are several options to pass back this information. If EJB is living in the same JVM,
you may as well use some singleton Map and store progress under certain key (session ID)

If this is not the case, you will need some shared state or comminucation. There are several options

  • store it on database accessible from both tiers ( sql, JNDI, LDAP - better solution would be key-value store , like redis - if you got it )
  • use some messaging to deposit state of processing on web tier side
  • store state in a hash it on EJB tier side, and provide another SLSB method to rtrieve this state

Your choice is not easy - all of these solution suckin a different ways.

浅忆 2024-12-27 23:41:37

我使用线程轮询模型结合 ProgressBar 组件来完成此操作。

public void init()
{
    // This method is called by the constructor.
    // It doesn't matter where you define the PortableRenderer, as long as it's before it's used.
    PushRenderer.addCurrentSession("fullFormGroup");
    portableRenderer = PushRenderer.getPortableRenderer();
}   


public void someBeanMethod(ActionEvent evt)
{
    // This is a backing bean method called by some UI event (e.g. clicking a button)
    // Since it is part of a JSF/HTTP request, you cannot call portableRenderer.render

    copyExecuting = true;

    // Create a status thread and start it

    Thread statusThread = new Thread(new Runnable() {
        public void run() {
        try {
                        // message and progress are both linked to components, which change on a portableRenderer.render("fullFormGroup") call
            message = "Copying...";
            // initiates render. Note that this cannot be called from a thread which is already part of an HTTP request
            portableRenderer.render("fullFormGroup"); 
            do {
                progress = getProgress();
                portableRenderer.render("fullFormGroup"); // render the updated progress
                Thread.sleep(5000); // sleep for a while until it's time to poll again
            } while (copyExecuting);
            progress = getProgress();
            message = "Finished!";
            portableRenderer.render("fullFormGroup"); // push a render one last time
        } catch (InterruptedException e) {
            System.out.println("Child interrupted.");
        }
    });
    statusThread.start();

    // create a thread which initiates script and triggers the termination of statusThread
    Thread copyThread = new Thread(new Runnable() {           
        public void run() {
        File someBigFile = new File("/tmp/foobar/large_file.tar.gz");
            scriptResult = copyFile(someBigFile); // this will take a long time, which is why we spawn a new thread
            copyExecuting = false; // this will caue the statusThread's do..while loop to terminate


        } 
    });
    copyThread.start();
}

I accomplished this using a threaded polling model in conjunction with a ProgressBar component.

public void init()
{
    // This method is called by the constructor.
    // It doesn't matter where you define the PortableRenderer, as long as it's before it's used.
    PushRenderer.addCurrentSession("fullFormGroup");
    portableRenderer = PushRenderer.getPortableRenderer();
}   


public void someBeanMethod(ActionEvent evt)
{
    // This is a backing bean method called by some UI event (e.g. clicking a button)
    // Since it is part of a JSF/HTTP request, you cannot call portableRenderer.render

    copyExecuting = true;

    // Create a status thread and start it

    Thread statusThread = new Thread(new Runnable() {
        public void run() {
        try {
                        // message and progress are both linked to components, which change on a portableRenderer.render("fullFormGroup") call
            message = "Copying...";
            // initiates render. Note that this cannot be called from a thread which is already part of an HTTP request
            portableRenderer.render("fullFormGroup"); 
            do {
                progress = getProgress();
                portableRenderer.render("fullFormGroup"); // render the updated progress
                Thread.sleep(5000); // sleep for a while until it's time to poll again
            } while (copyExecuting);
            progress = getProgress();
            message = "Finished!";
            portableRenderer.render("fullFormGroup"); // push a render one last time
        } catch (InterruptedException e) {
            System.out.println("Child interrupted.");
        }
    });
    statusThread.start();

    // create a thread which initiates script and triggers the termination of statusThread
    Thread copyThread = new Thread(new Runnable() {           
        public void run() {
        File someBigFile = new File("/tmp/foobar/large_file.tar.gz");
            scriptResult = copyFile(someBigFile); // this will take a long time, which is why we spawn a new thread
            copyExecuting = false; // this will caue the statusThread's do..while loop to terminate


        } 
    });
    copyThread.start();
}
香橙ぽ 2024-12-27 23:41:37

当您使用icefaces时,您可以使用 ICEpush 机制来渲染您的更新。

As you are using icefaces you could use the ICEpush mechanism for rendering your updates.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文