首页 文章

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

提问于
浏览
2

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

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

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

现在我试过:

  • doFilter

  • asyncContext

  • ExecutorService

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

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

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

是否有捷径可寻 ?

3 回答

  • 1

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

    servilla中的“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不会阻止这种情况) .

    可扩展性是 Contract 性的,了解我和读者的所有选项真的很有趣 . 而且我不知道我将在哪台服务器上托管我的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,并将继续研究它是否适合我的问题

  • 0

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

  • 1

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

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

相关问题