如何在JavaEE中响应后执行代码

时间:2016-07-25 16:00:15

标签: servlets java-ee executorservice

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

在我的情况下,服务器向我发送数据以便让我完成我的工作,但此操作可能很长(例如,向5000个联系人发送短信并检查是谁收到了它)。服务器立即要求HTTP 204 No Content响应以确保已收到数据。然后我的webapp将执行操作并在REST API上发送状态。

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

现在我试过了:

  • 的doFilter
  • asyncContext
  • 的ExecutorService

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

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

我无法使代码与Executors(新的)一起工作,但即使我在执行线程时遇到错误,我也希望发送HTTP 204并在另一方面处理错误。

有一种简单的方法吗?

3 个答案:

答案 0 :(得分:1)

我使用后台线程来轮询队列表,以查找在发送响应之前不必完成的事情,并且需要的时间超过约0.1秒。例如,电子邮件发件人线程处理发送电子邮件以响应用户的操作,例如“谢谢您的订单”消息。在这种情况下,servlet生成电子邮件并将其作为记录附加到状态为“准备发送”的电子邮件表中,然后让电子邮件发件人线程知道有新的电子邮件可以发送。电子邮件发件人线程抓取下一个最旧的“准备发送”消息,发送它,更新表并等待下一个。很酷的部分是从主程序发送电子邮件就像将记录附加到表格一样简单,并且每次都没有使用SMTP服务。我有一个单独但非常相似的线程来发送短信。如果后台进程无法处理负载并且开始落后,只要您小心确保它们不会尝试在队列中获取相同的记录,就可以轻松启动多个进程。

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

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

servlet中的“Vanilla”线程

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

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);
}

结果:我在邮递员的17ms内收到了回复

  

将发送回复。 / 1

     

完成了长时间的操作。 / 10011

servlet中的Spawn线程是针对Java EE规范的,而EJB在内部不起作用。 See herehere。以这种方式使用线程可能导致线程饥饿。每个服务器都不允许这样做(Tomcat不会阻止这种情况)。

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

ManagedExecutorService

ManagedExecutorService是Java EE 7的一部分。在我的例子中,项目以Java EE 6环境为目标,因此我使用了ExecutorService。之前我遇到过一个问题:我无法在异步中访问请求的正文,我发现this

  

Servlet 3.1中引入的异步请求体读取概念

但Servlet 3.1也是Java EE 7。所以我的runnable构造函数请求请求体作为String。

这是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作为dev,但如果你使用其他东西,你必须使用ManagedExecutorService,因为它可能会阻止你在servlet中启动线程。

我也很惊讶没有在stackoverflow上找到一个简单的快速示例。由于this article,我能够对其进行编码。

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