如何在 JavaEE 响应后执行代码

How to execute code after response in JavaEE

我尝试找到一种在响应后 运行 编码的方法,但没有成功。

在我的例子中,服务器向我发送数据以便让我完成我的工作,但此操作可能很长(例如,向 5000 个联系人发送 SMS 并检查谁收到了)。服务器期望立即收到 HTTP 204 No Content 响应以确保已收到数据。然后我的 webapp 将执行操作并在 REST API.

上发送状态

我的问题是:如何发送响应然后执行代码?

现在我尝试了:

在每种情况下,为了测试连接是否在我的操作结束之前关闭,我调用了一个外部 URL,它故意需要 10 秒来回答。每次,我的服务器都需要 10 秒才能回答。

我的 servlet 只是挂起,等待代码结束。

我无法使代码与 Executors 一起工作(这是新手),但即使我在线程执行期间遇到错误,我也想发送 HTTP 204 并在另一只手上处理错误。

有没有简单的方法来做到这一点?

我使用后台线程轮询队列 tables 以查找在发送响应之前不必完成的事情,这些事情将花费大约 0.1 秒以上的时间。例如,电子邮件发件人线程处理发送电子邮件以响应用户的操作,如 "thanks for your order" 消息。在这种情况下,servlet 生成电子邮件并将其作为记录附加到状态为 "ready to send" 的电子邮件 table,然后让电子邮件发件人线程知道有一封新电子邮件已准备好发送。电子邮件发件人线程获取下一个最早的 "ready to send" 消息,发送它,更新 table 并等待下一个。很酷的部分是从主程序发送电子邮件就像将记录附加到 table 一样简单,然后继续前进,而无需每次都使用 SMTP 服务。我有一个单独但非常相似的线程来发送短信。如果后台进程无法处理负载并开始落后,则启动多个进程非常容易,只要您小心确保它们不会尝试获取队列中的相同记录。

这是我对这个问题的结论:

"Vanilla" servlet 中的线程

这里是响应发送后成功执行的示例代码:

protected void doPost(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    final long startTime = System.currentTimeMillis(); //Start time to compare easily

    // MY ASYNC JOB
    Thread t1 = new Thread(new Runnable() {
        public void run()
        {
            try {
                Thread.sleep(10000);
                System.out.println("Long operation done.  /   " + (System.currentTimeMillis() - startTime));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }});  
    t1.start();
    // END OF MY ASYNC

    // Servlet code here
    System.out.println("Will send the response.  /   " + (System.currentTimeMillis() - startTime));
    response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}

结果:我在 Postman 中在 17 毫秒内收到了响应

Will send the response. / 1

Long operation done. / 10011

servlet 中的 Spawn 线程违反 Java EE 规范,EJB 无法在其中工作。 See here or here。以这种方式使用线程会导致线程饥饿。这在每台服务器上都是不允许的(Tomcat 不会阻止这种情况)。

可伸缩性是契约性的,了解我和读者的所有选项真的很有趣。而且我不知道我将在哪个服务器上托管我的 webapp!

托管执行器服务

ManagedExecutorService 是 Java EE 7 的一部分。在我的例子中,该项目的目标是 Java EE 6 环境,所以我改用了 ExecutorService。早些时候我遇到了一个问题:我无法在我的异步中访问请求的主体,我发现 :

Asynchronous Request Body read [...] concepts introduced in Servlet 3.1

但是 Servlet 3.1 也是 JavaEE 7。所以我的可运行构造函数要求请求主体作为字符串。

这里是 ServletContextListener 的示例代码:

public void contextInitialized(ServletContextEvent event) {
    //Executor
    executor = Executors.newCachedThreadPool();

    //Init Context
    app = event.getServletContext();
    app.setAttribute("executor", executor);
}

//Do not forget to implements contextDestroyed !
public void contextDestroyed(ServletContextEvent event) {
    try {
        executor.shutdown();
        while(!executor.awaitTermination(10, TimeUnit.SECONDS)){
            System.out.println("executor.awaitTermination");
        };
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

还有我的 servlet:

protected void doPost(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    final long startTime = System.currentTimeMillis(); //Start time to compare easily

    //Get my executor service and run a new async task
    ExecutorService serv = (ExecutorService) this.getServletContext().getAttribute("executor");
    serv.execute(new testAsync(startTime));

    // Servlet code here
    System.out.println("Will send the response.  /  " + (System.currentTimeMillis() - startTime));
    response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}


//My runnable
private class testAsync implements Runnable{ //Use Callable for Java 7+
    private long startTime;

    //Prior to Servlet 3.1, you have to give the request body instead of using asyncContext
    public testAsync(long pstart){
        this.startTime = pstart;
    }

    @Override
    public void run(){
        try {
            Thread.sleep(10000);
            System.out.println("Long operation done.  /   " + (System.currentTimeMillis() - this.startTime));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这个示例有效并且对我来说似乎是最好的解决方案,因为我必须在我的异步任务中进行多线程处理。我将 Tomcat 用于开发,但如果您使用其他东西,则必须使用 ManagedExecutorService,因为它可能会阻止您在 servlet 中启动线程。

我也很惊讶没有在 Whosebug 上找到一个简单的快速示例。多亏了 this article.

我才能够编写代码

编辑:我当时并不知道 JMS,将继续研究它是否适合我的问题

我认为最简单的方法是使用另一个从您的上下文中调用 @Asynchrous 方法的 bean。 参见 Asynchronous Method Invocation

另一种更复杂的方法可以是使用 CDI 事件,您可以将其与成功的交易相结合(例如,在更复杂的响应逻辑的情况下)。 参见 Using Events in CDI Applications