Como executar uma tarefa em segundo plano em um aplicativo da web baseado em servlet?

97

Estou usando Java e quero manter um servlet em execução contínua em meu aplicativo, mas não estou sabendo como fazer isso. Meu servlet tem um método que fornece contagens diárias de usuários de um banco de dados, bem como a contagem total de usuários de todo o banco de dados. Portanto, quero manter o servlet em execução contínua para isso.

pritsag
fonte
O que você quer dizer com "correr continuamente"?
skaffman
1
o que você quer dizer com correr continuamente? Ele será executado enquanto seu servidor de aplicativos
estiver em
2
Eu não entendo porque ele tem que rodar continuamente ... se alguém quer a 'contagem de usuários' então eles chamam seu método servlet e você dá a eles?
trojanfoe
@trojanfoe Na verdade, eu quero o usercount diariamente, então, para isso, terei que executar o servlet manualmente todos os dias, então, em vez de fazer isso, quero executar o servlet continuamente. portanto, não precisarei executar o servlet todos os dias.
pritsag de
1
@pritsag: um servlet existe para atender às solicitações do usuário, não para executar trabalhos em lote.
skaffman

Respostas:

217

Seu problema é que você entendeu mal o propósito do servlet . Sua intenção é agir em solicitações HTTP, nada mais. Você quer apenas uma tarefa em segundo plano que é executada uma vez por dia.

EJB disponível? Usar@Schedule

Se acontecer de seu ambiente suportar EJB (ou seja, um servidor Java EE real, como WildFly, JBoss, TomEE, Payara, GlassFish, etc), use em seu @Schedulelugar. aqui estão alguns exemplos:

@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.
    }

} 

Sim, isso é realmente tudo. O contêiner irá retirá-lo e gerenciá-lo automaticamente.

EJB indisponível? UsarScheduledExecutorService

Se o seu ambiente não suportar EJB (ou seja, você não está usando um servidor Java EE real, mas um servletcontainer barebones como Tomcat, Jetty, etc), use ScheduledExecutorService. Isso pode ser iniciado por a ServletContextListener. Aqui está um exemplo inicial:

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

}

Onde as classes de trabalho são assim:

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.
    }

}

Nunca pense em usar java.util.Timer/ java.lang.Threadem um ambiente baseado em Java EE / Servlet

Por último, mas não menos importante, nunca use diretamente java.util.Timere / ou java.lang.Threadem Java EE. Esta é a receita para problemas. Uma explicação elaborada pode ser encontrada nesta resposta relacionada ao JSF na mesma pergunta: Gerando threads em um bean gerenciado JSF para tarefas agendadas usando um cronômetro .

BalusC
fonte
9
@BalucS Obrigado senhor, sua solução me ajudou e eu aprendi sobre ScheduledExecutorService que era novo para mim como sou novo em java. Obrigado mais uma vez.
pritsag de
@BalusC: Onde a classe UpdateCounts deve ser colocada no web.xml?
Ashwin
1
@Ashwin web.xml é um descritor de implantação . A classe UpdateCount não está relacionada à implantação, portanto, não precisa ser colocada em web.xml
informatik01
12
Um problema crucial com um ScheduledExecutorService: Certifique-se de capturar todas as exceções em seu executor. Se uma exceção escapar do seu runmétodo, o executor interromperá a execução silenciosamente. Este é um recurso, não um bug. Leia o documento e estude com algumas pesquisas no Google.
Basil Bourque
1
@Agi: isso acontecerá se scheduler.shutdownNow()não for invocado corretamente conforme o exemplo. Se isso não for invocado, o encadeamento de agendamento continuará em execução.
BalusC
4

Eu sugeriria o uso de uma biblioteca como o quartzo para executar a tarefa em intervalos regulares. O que o servlet realmente faz? Ele envia um relatório para você?

Twister
fonte
sim, me dá a contagem do usuário criado por dia e também a contagem do total de usuários em meu banco de dados.
pritsag
1
huuu? Você pode descrever a arquitetura COMPLETA do seu sistema. Estou perdido.
Twister
@Twister sou novo em java e em fase de aprendizagem, senhor, e realmente não sei muito sobre servlets.
pritsag de
O problema não é sobre servlet. De que aplicativo você está falando? (ps: é uma má ideia deletar seus comentários, especialmente aqueles aos quais eu respondi)
Twister
@twister quando o usuário acessar o aplicativo, ele obterá todos os detalhes como quantos usuários foram criados hoje, quantos usuários foram criados até agora, etc. e eu quero executar o servlet em segundo plano continuamente para que o usuário possa obter as atualizações .Eu sei que esta não é a explicação adequada. (Ps: eu sei que foi uma má ideia. Desculpe por isso.)
pritsag
3

Implementar duas classes e chamar startTask()em main.

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();
    }
}
Ali Alimohammadi
fonte
3
Definitivamente, NÃO é assim que se faz em um aplicativo da web - veja a resposta de @BalusC acima - ele está correto aqui e eu diria que você pode confiar em todas as suas respostas.
Yoshiya
0

Em um sistema de produção que pode ter vários contêineres que não sejam jee em execução. Use um planejador corporativo como o planejador Quartz, que pode ser configurado para usar um banco de dados para maamgememt de tarefas.

Jeryl Cook
fonte