如何在基于servlet的Web应用程序中运行后台任务?

104

我正在使用Java,希望在我的应用程序中保持一个servlet的持续运行,但我不知道如何做到这一点。我的servlet有一个方法,每天从数据库中获取用户数量以及整个数据库的用户总数。因此,我希望为此保持servlet的持续运行。


你的意思是“持续运行”? - skaffman
1
什么是持续运行?只要您的应用服务器在运行,它就会一直运行。 - fmucar
2
我不明白为什么它必须持续运行...如果有人想要“用户计数”,那么他们调用你的servlet方法,然后你给他们就可以了? - trojanfoe
@trojanfoe,我实际上想要每天的用户数量统计,所以我必须每天手动运行servlet,但是我不想这样做,我想连续运行servlet,这样我就不需要每天运行servlet了。 - pritsag
1
@sag: Servlet 用于响应用户请求,而不是运行批处理作业。 - skaffman
显示剩余4条评论
5个回答

225
你的问题在于你误解了servlet的目的。它只是用来处理HTTP请求,没有其他作用。你只需要一个每天运行一次的后台任务。

有EJB可用?使用@Schedule

如果你的环境支持EJB(即真正的Java EE服务器,如WildFly、JBoss、TomEE、Payara、GlassFish等),那么使用@Schedule。以下是一些示例:

@Singleton
public class BackgroundJobManager {

    @Schedule(hour="0", minute="0", second="0", persistent=false)
    public void someDailyJob() {
        // Do your job here which should run every start of day.
    }

    @Schedule(hour="*/1", minute="0", second="0", persistent=false)
    public void someHourlyJob() {
        // Do your job here which should run every hour of day.
    }

    @Schedule(hour="*", minute="*/15", second="0", persistent=false)
    public void someQuarterlyJob() {
        // Do your job here which should run every 15 minute of hour.
    }

    @Schedule(hour="*", minute="*", second="*/5", persistent=false)
    public void someFiveSecondelyJob() {
        // Do your job here which should run every 5 seconds.
    }

} 

没错,就是这样。容器将自动获取和管理它。

EJB不可用?使用ScheduledExecutorService

如果您的环境不支持EJB(即您不是使用真正的Java EE服务器,而是使用裸机Servlet容器,如Tomcat、Jetty等),那么请使用ScheduledExecutorService。这可以通过ServletContextListener进行初始化。下面是一个启动示例:

@WebListener
public class BackgroundJobManager implements ServletContextListener {

    private ScheduledExecutorService scheduler;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(new SomeDailyJob(), 0, 1, TimeUnit.DAYS);
        scheduler.scheduleAtFixedRate(new SomeHourlyJob(), 0, 1, TimeUnit.HOURS);
        scheduler.scheduleAtFixedRate(new SomeQuarterlyJob(), 0, 15, TimeUnit.MINUTES);
        scheduler.scheduleAtFixedRate(new SomeFiveSecondelyJob(), 0, 5, TimeUnit.SECONDS);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        scheduler.shutdownNow();
    }

}

工作类看起来像这样:

public class SomeDailyJob implements Runnable {

    @Override
    public void run() {
        // Do your daily job here.
    }

}
public class SomeHourlyJob implements Runnable {

    @Override
    public void run() {
        // Do your hourly job here.
    }

}
public class SomeQuarterlyJob implements Runnable {

    @Override
    public void run() {
        // Do your quarterly job here.
    }

}
public class SomeFiveSecondelyJob implements Runnable {

    @Override
    public void run() {
        // Do your quarterly job here.
    }

}

永远不要在Java EE / Servlet环境中使用java.util.Timer/java.lang.Thread

最后,绝不要直接在Java EE中使用java.util.Timer和/或java.lang.Thread。这是引发问题的根源。可以在此JSF相关答案中找到详细解释:使用计时器在JSF管理的bean中生成线程以进行定时任务


9
@BalucS先生,非常感谢您的解决方案,它帮助了我并让我了解到ScheduledExecutorService,因为我是Java的新手。再次感谢您。 - pritsag
1
@Ashwin web.xml 是一个部署描述符。类UpdateCount与部署无关,因此不必放在web.xml中。 - informatik01
12
ScheduledExecutorService 的一个重要问题是:一定要在你的执行器中捕获所有异常。如果从你的 run 方法中逃逸了异常,执行器就会默默地停止执行。这不是错误,而是一个特性。请阅读文档并通过搜索学习相关知识。 - Basil Bourque
@BalusC,org.springframework.scheduling.concurrent.ScheduledExecutorTask 怎么样? - ACV
1
@Agi:如果没有按照示例正确调用scheduler.shutdownNow(),那么就会发生这种情况。如果没有调用它,那么计划线程确实会继续运行。 - BalusC
显示剩余7条评论

4

我建议使用像quartz这样的库来定期运行任务。Servlet到底是做什么的?它会发送给您一份报告吗?


1
huuu?你能描述一下你的系统的完整架构吗?我感到有点迷失。 - Twister
@Twister,我是Java的新手,正在学习阶段,对于Servlets并不了解。 - pritsag
当用户启动应用程序时,@twister会提供所有详细信息,例如今天创建了多少用户,到目前为止创建了多少用户等。我希望能够在后台持续运行servlet,以便用户可以获得更新。我知道这不是一个恰当的解释。(PS:我知道这是个糟糕的想法,对此我感到抱歉。) - pritsag
@pritsag 在你的数据库中,你可以将用户创建的日期/时间附加到用户帐户上,并在用户在网页上点击按钮时进行计数。这种方法更加强大和灵活,因为你可以决定要计算哪个时间间隔。但是,如果你真的想要一个类似于cron的系统,例如每天、每小时等,你可以使用quartz在特定的时间间隔内运行任务。 - gigadot
@gigadot 感谢您的回复。实际上,我已经在servlet中实现了该方法,它可以完美地给出计数。但是,现在我想要的是,不再每天运行servlet,而是让它持续运行,以便每当发送有关计数更新的请求时,都能向用户发送响应。 - pritsag
显示剩余3条评论


1

实现两个类并在main中调用startTask()

public void startTask()
{
    // Create a Runnable
    Runnable task = new Runnable() {
        public void run() {
            while (true) {
                runTask();
            }
        }
    };

    // Run the task in a background thread
    Thread backgroundThread = new Thread(task);
    // Terminate the running thread if the application exits
    backgroundThread.setDaemon(true);
    // Start the thread
    backgroundThread.start();
}

public void runTask()
{
    try {
        // do something...         
        Thread.sleep(1000);

    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

3
这绝对不是在Web应用程序中执行的方式-请参考@BalusC上面的答案-他在这里是正确的,我会说你可以信任他所有的回答。 - Yoshiya

0
在一个可能有多个非 JEE 容器运行的生产系统中,使用另一个企业级调度程序,如 Quartz 调度程序,它可以配置为使用数据库进行任务管理。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接