Sobre esse Tutorial

Néki Technologies.

Agradecimentos ao Néki Team!

Introdução

Um sistema de agendamento de tarefas (job scheduler) que pode ser integrado com qualquer outro sistema. O termo agendamento de tarefas (job scheduler) pode trazer idéias diferentes para várias pessoas. Ao longo desse tutorial você terá uma idéia concisa sobre esse termo. Em resumo, job scheduler é um sistema que tem como responsabilidade executar (ou notificar) outros componentes de software quando um pré-determinado horário ocorre.

Sendo totalmente flexível, o Quartz contém vários modelos que podem ser usado separadamente ou juntos, atendendo suas necessidades, e permite você escrever seu código de maneira mais natural possível para o seu projeto.

Necessita de pouquíssima configuração e pode ser usado como "out-of-the-box" se suas necessidades forem relativamente básicas.

Quartz é "fault-tolerant" e pode persistir os jobs entre "restarts" do servidor de aplicação.

Apesar do Quartz ser extremante proveitoso para execução de processos simples em "schedules", o potencial desse framework somente será percebido quando você aprender em como utiliza-lo para controlar os fluxos dos processos de negócio de suas aplicações.

Olhando através de uma perspectiva de componente

Quartz é distribuído com uma pequena biblioteca Java (.jar) que contém todas as principais funcionalidades desse framework. A principal API para essas funcionalidades é a Scheduler. Ela prove operações simples tais como: scheduling/unscheduling de jobs, starting/stopping/pausing de scheduler.

Se você deseja agendar a execução de seus próprios componentes, então você deve implementar a interface de um simples Job, o qual contém o método execute(). Caso seja necessário ter componentes notificados quando um determinado agendamento ocorre, então os componentes devem implementar as interfaces TriggerListener ou JobListener.

O principal processo do Quartz pode ser iniciado e executado dentro da sua aplicação, como uma aplicação stand-alone (com uma user interface), ou dentro de servidor de aplicações J2EE para ser utilizado como recurso para os componentes de sua aplicação.

Usando o Quartz

Antes de utilizar o scheduler é preciso instancia-lo. Para fazer isso você utiliza a Factory SchedulerFactory. Alguns usuários do Quartz gostam de manter a instancia da factory serializada no armazenamento JNDI, mas outros preferem (por ser mais fácil) instanciar e usar a factory diretamente (como o exemplo abaixo).

Uma vez que o scheduler é instanciado, ele pode ser iniciado, pausado ou terminado. Note que uma vez que um scheduler é terminado, ele não pode ser reiniciado sem ser instanciado novamente. As Triggers não são disparadas (jobs não são executados) até que o scheduler tenha sido iniciado.

Aqui um pequeno trecho de código, que instancia e inicia um scheduler, e agenda um job para ser executado:

  SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();

  Scheduler sched = schedFact.getScheduler();

  sched.start();

  JobDetail jobDetail = new JobDetail("myJob", sched.DEFAULT_GROUP, DumbJob.class);

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",sched.DEFAULT_GROUP,new Date(),null,0,0L);

  sched.scheduleJob(jobDetail, trigger);

Como você pode ver, trabalhar com o Quartz é simples. Nós iremos agora gastar um pouco de tempo falando sobre Jobs e Triggers, então você entenderá mais a fundo esse exemplo.

Jobs e Triggers

Como mencionado anteriormente, você pode fazer componentes Java serem executados por um scheduler, simplesmente implementando a interface Job. Aqui esta a interface:

  package org.quartz;

  public interface Job {

    public void execute(JobExecutionContext context) throws JobExecutionException{
  }

Nesse caso você não poderia adivinhar, quando a trigger do Job é disparada (mais do que em um momento), o método "execute" é invocado pelo scheduler. O objeto JobExecutionContext que é passado para este método, prove a instancia do job com informações sobre seu ambiente "run-time" - um handle para o Scheduler que é executado, um handle para a Trigger que dispara a execução, o JobDetail e alguns outros itens.

O objeto JobDetail é criado pela sua aplicação e no momento do Job é adicionado para o scheduler. Ele contém várias configurações para o Job, como o JobDataMap, o qual pode ser usado como um armazenador de informações de estado para uma dada instancia da sua classe de job.

Objetos Triggers são usados para disparar a execução de jobs. Quando você deseja agendar um job, você instancia uma trigger e configura suas propriedades para prover um agendamento que você deseja. Existe atualmente dois tipos de triggers: SimpleTrigger e CronTrigger.

SimpleTrigger é um gerenciador se você precisa de uma execução (uma simples execução de seu job em um determinado momento), ou se você precisa disparar um job em um determinado momento e tem que repetir N vezes, com um delay de T entre cada execução. CronTrigger é usado se você deseja ter lançamentos baseados em agendamento "calendar-like" - como toda sexta-feira, a noite ou as 10:15 do dia 10 de todo mês.

Por quê Jobs e Triggers? Muitos jobs agendados não tem noções de separação entre jobs e triggers. Alguns definem um job como uma simples execução em um determinado momento junto com alguns pequenos identificadores. Enquanto desenvolvendo Quartz, foi decidido que faria sentido criar uma separação entre o schedule e o trabalho para executar o schedule. Isto tem muitos benefícios.

Por exemplo, Jobs podem ser criados e armazenados em um job scheduler independente de uma trigger, e muitas triggers podem ser associadas com o mesmo job. Um outro beneficio desse baixo acoplamento é a habilidade de configurar jobs que ficam no scheduler após serem associadas triggers e terem expiradas, então ele pode ser agendado novamente mais tarde, sem ter que redefini-lo. Isso também permite você modificar ou substituir trigger sem ter que redefinir sua associação com o job.

Identificadores

Jobs e Triggers são dados nomes identificadores como eles são registrados no scheduler do Quartz. Jobs e triggers podem também estar juntos dentro de grupos os quais podem ser proveitosos para organização se seus jobs e triggers dentro de categorias para manutenções futuras. O nome de um job ou trigger devem ser únicos dentro do grupo - em outras palavras, o verdadeiro identificador de um job ou trigger é o "nome+grupo".

Agora o que você tem é uma idéia geral sobre Jobs e Triggers, falaremos mais sobre eles em detalhe.

Mais sobre Jobs e JobDetails

Como você pode ver, Jobs são mais fáceis para implementar, Existem apenas poucas coisas que você precisa entender sobre a natureza de jobs, sobre o método execute da interface Job e sobre o JobDetails.

Enquanto a classe que você implementa é a atual "job", Quartz precisa ser informado sobre vários atributos que você deseja que o job tenha. Isto é feito através da classe JobDetail, a qual foi mencionada rapidamente na seção anterior.

Nesse momento vamos ver um pouco sobre a natureza do ciclo de vida do Job e das instancias job dentro do Quartz. Primeiro vamos voltar ao pequeno trecho de código que vimos anteriormente.

  JobDetail jobDetail = new JobDetail("myJob",             // nome do job
                                      sched.DEFAULT_GROUP, // grupo do job
                                      DumbJob.class);        // a classe Java que será executada

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",
                                            sched.DEFAULT_GROUP,
                                            new Date(),
                                            null,
                                            0,
                                            0L);

  sched.scheduleJob(jobDetail, trigger);
  
Agora considere a classe "DumJob" definida como:



  public class DumbJob implements Job {

    public DumbJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      System.err.println("DumbJob esta sendo executado.");
    }
  }

Percebe que nós informamos ao scheduler uma instancia de JobDetail, e que esse refere ao job que será executado simplesmente provendo a classe do job. Cada tempo que o scheduler executa o job, ele cria uma nova instancia da classe antes de chamar o método execute. Uma das ramificações desta ação é o fato de que jobs devem ter um construtor sem parâmetros. Uma outra ramificação é que não faz sentido ter definido "data-members" na classe do job - como seus valores podem ser limpos a cada execução.

Talvez agora você esteja esperando para perguntar "Como eu posso prover propriedades/configurações para a instancia de um Job?" e "Como eu posso manter ficar de olho no estado de um job entre as execuções?". A resposta para essas questões é a mesma: o segredo esta em JobDataMap, o qual é parte do objeto JobDetail.

O JobDataMap pode ser usado para segurar qualquer quantidade de objetos (serializados) que você deseja estar disponível para a instancia do job quando executado. JobDataMap é uma implementação da interface Java Map, e tem alguns métodos adicionais para armazenar e obter dados de tipos primitivos.

Aqui alguns pequenos exemplos de adicionar dados dentro do JobDataMap anterior ao scheduler para o job:

  jobDetail.getJobDataMap().put("jobSays", "Ola Mundo!");
  jobDetail.getJobDataMap().put("myFloatValue", 3.141f);
  jobDetail.getJobDataMap().put("myStateData", new ArrayList());

Aqui um exemplo de obtendo dados do JobDataMap durante a execução de job's:

  public class DumbJob implements Job {

    public DumbJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      String instName = context.getJobDetail().getName();
      String instGroup = context.getJobDetail().getGroup();

      JobDataMap dataMap = context.getJobDetail().getJobDataMap();

      String jobSays = dataMap.getString("jobSays");
      float myFloatValue = dataMap.getFloat("myFloatValue");
      ArrayList state = (ArrayList)dataMap.get("myStateData");
      state.add(new Date());

      System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);
    }
  }

Se você usa uma persistência JobStore (falaremos sobre JobStore neste tutorial) você deve ter alguns cuidados na decisão sobre o que colocar no JobDataMap, porque o objeto será serializado, e eles entretanto tornam-se sujeitos a problemas de versionamento de classe. Obviamente tipos padrões do Java devem ser seguros, mas fora isso, qualquer momento mudanças na definição de uma classe para a qual você teria serializado instancias, cuidado para não ter problemas de incompatibilidade.

Informações adicionais para esse tópico podem ser achadas na "Java Developer Connection Tech Tip: Serialization In The Real World". Opcionalmente você pode colocar JDBC-JobStore e JobDataMap dentro de um modelo onde somente tipos primitivos e strings podem ser armazenadas no "map", portanto, eliminando qualquer possibilidade de problemas futuros com serialização.

Stateful vs. Non-Stateful Jobs

Agora algumas informações adicionais sobre estado de dados (JobDataMap): A instancia de um Job pode ser definido como "stateful" ou "non-stateful". Jobs Non-stateful somente tem seus JobDataMap armazenados no momento em que são adicionadas ao scheduler. Isto significa que qualquer mudança feita nos conteúdos dos dados do job no "map" durante a execução do job ele irá se perder, e não será mais visto pelo job na próxima vez que for executado. Você provavelmente deve estar tentando adivinhar, o job stateful é apenas o oposto - seu JobDataMap é rearmazenado após toda execução do job. Um efeito colateral fazendo o job stateful é que ele não pode ser executado concorrentemente. Em outras palavras: se um job é stateful, e uma trigger tenta disparar o job enquanto ele já esta sendo executado, a trigger será bloqueada (wait) até que a execução termine.

Você "marca" um Job como stateful através da implementação da interface StatefulJob, ao invés da interface Job.

Agora fica claro que você pode criar uma classe de job e armazenar várias instancias dele dentro do scheduler pela criação de múltiplas instancias de JobDetails - cada qual com seu conjunto de configurações e JobDataMap - e adicionando todos aos scheduler.

Outros atributos de Jobs

Aqui um rápido sumario de outras propriedades que podem ser definidas para instancias do job através do objeto JobDetail:

  • Durability - se um job é non-durable, ele é automaticamente apagado do scheduler uma vez que não tenha mais qualquer trigger ativa associada a ele.
  • Volatility - se um job é volatile, ele não é persistido entre "restarts" do scheduler do Quartz.
  • RequestsRecovery - se um job é "requests recovery" e durante a sua execução ocorre algum problema de "crashe" ou a maquina é desligada, então o job é re-executado quando o scheduler for iniciado novamente. Neste caso, o método JobExecutionContext.isRecovering() irá retornar true.
  • JobListeners - um job pode ter um conjunto de zero ou mais JobListeners associado a ele. Quando o job é executado, os listeners são notificados.

O método Job.execute

Finalmente, é preciso informar alguns detalhes sobre o método Job.execute. O único tipo de exception que esta permitido lançar a partir do método execute é a JobExecutionException (incluindo RuntimeExceptions). Isto porque deve geralmente o conteúdo do método execute estar em um bloco "try-catch". Você deve também gastar tempo procurando na documentação pela JobExecutionException, como seu job pode usa-la, isto prove varias diretivas ao scheduler dependendo de como você gerenciar a exception.

Mais sobre Triggers

Como jobs, triggers são relativamente fáceis de trabalhar, mas contém uma variedade de opções para customizar que você precisa estar atento e entender antes de você fazer uso total do Quartz. Também, como visto antes, existem diferentes tipos de triggers que você pode escolher de acordo com suas necessidades de scheduling.

Calendars

Os objetos Calendar do Quartz (não são java.util.Calendar) podem ser associados com triggers no momento que a trigger é armazenada no scheduler. Calendars são úteis para controlar intervalos de tempo no disparo de triggers. Para uma instancia de um objeto de negócio, você pode criar uma trigger que dispara um job todo dia da semana as 9:30 am, então adicione um Calendar que exclui todos os feriados que estão em dias comerciais.

Qualquer objeto serializável que implemente a interface Calendar pode ser um Calendar para as triggers:

  package org.quartz;

  public interface Calendar {

    public boolean isTimeIncluded(long timeStamp);

    public long getNextIncludedTime(long timeStamp);

  }

Perceba que os parâmetros desses métodos são do tipo "long", pois precisam estar no formato milissegundo. Isso significa que o Calendar pode trabalhar com o intervalo de tempo em milissegundo, mas na maioria das vezes você trabalhará com intervalos de dias. Para facilitar o seu trabalho, o Quartz inclui a classe org.quartz.impl.HolidayCalendar.

O Calendar deve ser instanciado e registrado no scheduler através do método addCalendar(). Se você usa o HolidayCalendar, após a instancia-lo, você deve usar o método addExcludedDate(Date date) para adicionar os dias que deverão ser excluídos do scheduler. A mesma instancia do Calendar pode ser usada com várias triggers:

  HolidayCalendar cal = new HolidayCalendar();
  cal.addExcludedDate( someDate );

  sched.addCalendar("myHolidays", cal, false);

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",
                                            sched.DEFAULT_GROUP,
                                            new Date(),
                                            null,
                                            SimpleTrigger.REPEAT_INDEFINITELY,
                                            60L * 1000L);
  trigger.setCalendarName("myHolidays");

  // .. agendando o job com trigger

  SimpleTrigger trigger2 = new SimpleTrigger("myTrigger",
                                             sched.DEFAULT_GROUP,
                                             new Date(),
                                             null,
                                             5,
                                             5L * 24L * 60L * 60L * 1000L);

  trigger2.setCalendarName("myHolidays");

  // .. agendando o job com trigger2

Os detalhes dos valores passados para o construtor de SimpleTrigger serão explicados na próxima seção. Nesse momento apenas acredite que o código cria duas triggers: uma que irá repetir a cada 60 segundos para sempre, e a outra que irá repetir cinco vezes com intervalo de cinco dias.

Erro de disparo

Uma outra propriedade importante da trigger é a "misfire instruction". Se ocorrer um erro de disparo com uma trigger persistente, perdendo o disparo por causa de um problema com o scheduler. Existem diferentes tipos de "misfire instructions" para os diferentes tipos de triggers. Pelo padrão é usado a instrução "smart policy" - a qual tem um comportamento dinâmico baseado no tipo de trigger e configurações. Quando o scheduler inicia, ele procura por qualquer tipo de trigger persistente que tem erro de disparo e então atualiza cada uma baseada nas suas configurações individuais de "misfire instructions". Quando você inicia seus projetos usando Quartz, você deve estar familiarizado com "misfire instructions" definidos para os tipos de triggers. Os "misfire instructions" podem ser configurados para as instancias de triggers usando o método setMisfireInstruction(..).

TriggerUtils

A classe TriggerUtils (package org.quartz.helpers) pode auxiliar a criar triggers e agendamento sem ter que se preocupar com objetos java.util.Calendar. Use essa classe para facilitar a criação de triggers que serão disparadas a cada minuto, hora, dia, semana, mês, etc. Também use essa classe para gerar datas que estão próximas de segundos, minutos e horas - isto é muito útil para configurar o tempo de inicio das triggers.

TriggerListeners

Finalmente, as triggers devem ter "listeners" registrados, como os jobs. Para isso basta implementar a interface TriggerListener a qual receberá notificações como um disparo de uma trigger.

Agora iremos falar um pouco sobre os tipos de triggers.

SimpleTrigger

SimpleTrigger deve ajuda-lo se você precisa executar um job em um momento especifico ou em um momento seguido por repetições em um intervalo especifico. Com esta descrição é fácil perceber que as configurações dessa trigger incluem um start-time, end-time, repeat count e um repeat interval. Cada uma dessas propriedades é exatamente o que você espera que seja, somente com uma particularidade para end-time.

A propriedade "repeat count" pode ser zero, um número positivo e inteiro ou uma constante como SimpleTrigger.REPEAT_INDEFINITELY. A propriedade "repeat interval" pode ser zero, ou um numero positivo e do tipo long representando o numero de milissegundos.

A propriedade "end-time" sobrescreve a propriedade "repeat count". Isso pode ser muito útil se você deseja criar um trigger que será disparado a cada 10 segundos até um determinado momento. Ao invés de calcular o número de vezes que ocorrerão as repetições entre o "start-time" e "end-time", você simplesmente especificar o "end-time" e então usar o "repeat count" como REPEAT_INDEFINITELY.

SimpleTrigger tem poucos construtores diferentes, a seguir iremos analisar esse:

  public SimpleTrigger(String name, String group, Date startTime,
                       Date endTime, int repeatCount, long repeatInterval)

SimpleTrigger exemplo 1 - Cria uma trigger que dispara exatamente uma vez, 10 segundos a partir de agora:


  long startTime = System.currentTimeMillis() + 10000L;

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",
                                            sched.DEFAULT_GROUP,
                                            new Date(startTime),
                                            null,
                                            0,
                                            0L);

SimpleTrigger Exemplo 2 - Criar uma trigger que dispara imediatamente, e repete a cada 60 segundos sem parar:


  SimpleTrigger trigger = new SimpleTrigger("myTrigger",
                                            sched.DEFAULT_GROUP,
                                            new Date(),
                                            null,
                                            SimpleTrigger.REPEAT_INDEFINITELY,
                                            60L * 1000L);

SimpleTrigger Exemplo 3 - Cria uma trigger que dispara imediatamente, e repete a cada 10 segundos até 40 segundos a partir de agora:


  long endTime = System.currentTimeMillis() + 40000L;

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",
                                            sched.DEFAULT_GROUP,
                                            new Date(),
                                            new Date(endTime),
                                            SimpleTrigger.REPEAT_INDEFINITELY,
                                            10L * 1000L);

SimpleTrigger Exemplo 4 - Cria uma trigger que dispara no dia 17 de Março de 2004 precisamente as 10:30 am e repete cinco vezes com 30 segundos de diferença entre cada disparo:

  java.util.Calendar cal = new java.util.GregorianCalendar(2002, cal.MARCH, 17);
  cal.set(cal.HOUR, 10);
  cal.set(cal.MINUTE, 30);
  cal.set(cal.SECOND, 0);
  cal.set(cal.MILLISECOND, 0);

  Data startTime = cal.getTime()

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",
                                            sched.DEFAULT_GROUP,
                                            startTime,
                                            null,
                                            5,
                                            30L * 1000L);

Não deixe de ler cada um dos construtores e saber qual é o mais conveniente para você.

SimpleTrigger e suas 'Misfire Instructions'

SimpleTrigger tem várias instruções que podem ser usadas para informar ao Quartz o que deve acontecer quando um erro de disparo ocorre. Essas instruções são definidas como constantes em SimpleTrigger:

				
  MISFIRE_INSTRUCTION_FIRE_NOW
  MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
  MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
  MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
  MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

Todas as triggers tem como "misfire instructions" padrão a Trigger.MISFIRE_INSTRUCTION_SMART_POLICY. Se a instrução "smart policy" é usada, SimpleTrigger dinamicamente escolhe entre suas várias "MISFIRE instructions", baseada na configuração e estado da instancia SimpleTrigger. No JavaDOC você poderá obter mais informações sobre o método SimpleTrigger.updateAfterMisfire().

CronTrigger

CronTriggers são mais úteis que as SimpleTrigger, pois se você precisar agendar job que serão disparados utilizando informações de calendário, e não simplesmente intervalos.

Com CronTrigger, você pode especificar agendamento como toda sexta-feira ou todo dia as 9:30 am.

Cron Expressions

Cron-Expressions são utilizadas para configurar instancias de CronTrigger. Cron-Expressions são strings que são divididas em sei partes que descrevem detalhes individuais do schedule. Essas strings (sub-expressions) são separadas com espaço em branco e representam:

  • Segundos
  • Minutos
  • Horas
  • Dias do mês
  • Mês
  • Dias da semana

Um exemplo de um cron-expression completa é a string "0 0 12 ? * WED" - a qual significa todas as quartas-feiras as 12:00 pm.

"Sub-expressions" podem conter intervalos e/ou listas. Por exemplo, o campo dia da semana pode conter "WED", "MON-FRI" ou "MON-WED, SAT".

Wild-cards (o caractere '*') são utilizados nos campos quando se quer utilizar todas as possibilidades. Um * no campo Dias da semana (Day-Of-Week) significa que estará sendo considerado todos os dias da semana.

Todos os campos tem um conjunto de valores válidos que podem ser especificados. Esses valores devem estar corretos - por exemplo números entre 0 e 59 para segundos e minutos, 0 e 23 para horas, 0 e 31 para dias do mês, 0 e 11 para meses do anos, mas também pode usar JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV e DEC, 1 e 7 para dias da semana (1=domingo) ou usar SUN, MON, TUE, WED, THU, FRI e SAT.

O caractere "/" pode ser utilizado para especificar incrementos para valores. Por exemplo se você utilizar "0/15" para o campo minutos, isso significa que a cada 15 minutos, iniciando no minuto 0. Se você usar "3/20" para o campo minutos isso significa a cada 20 minutos durante a hora, iniciando no minuto 3 - isso é o mesmo que especificar "3,23,43" para o campo minutos.

O caractere "?" é permitido para dias do mês e dias da semana. Ele é usado para especificar "valores sem especificação". Isto é muito útil quando você precisa especificar algo em um ou dois campos, mas não nos outros. Nos exemplos a seguir isso ficará mais claro.

O caractere "L" é permitido para dias do mês e dias da semana. Este caractere é usado por ultimo, mas ele faz diferença em cada um desses campos. Por exemplo: o valor "L" no campo dias do mês significa o ultimo dia do mês - dia 31 para Janeiro, 28/29 para Fevereiro e etc. Se for usado para dias da semana, isso significa 7 ou Sábado. Mas se for utilizado para dias da semana após outro valor, isso significa o ultimo dia "xxx" do mês - por exemplo: se você usar "6L" ou "FRIL", significa a ultima sexta do mês. Não utilize o caractere "L" em listar ou intervalos, você pode obter resultados não agradáveis.

Alguns exemplos:

CronTrigger Exemplo 1 - Uma expressão para criar uma trigger que simplesmente dispara a cada 5 minutos. "0 0/5 * * * ?"

CronTrigger Exemplo 2 - Uma expressão para criar uma trigger que dispara a cada 5 minutos, após 10 segundos após o inicio do minuto (ex.: 10:00:10 am, 10:05:10 am, etc.). "10 0/5 * * * ?"

CronTrigger Exemplo 3 - Uma expressão para criar uma trigger que dispara as 10:30, 11:30, 12:30 e 13:30 toda quarta e sexta. "0 30 10-13 ? * WED,FRI"

CronTrigger Exemplo 4 - Uma expressão para criar uma trigger que dispara a cada meia hora entre 8 am e 10 am todo dia cinco e vinte de cada mês. Atenção, não sera disparado evento as 10 am, apenas 8:00, 8:30, 9:00 e 9:30 "0 0/30 8-9 5,20 * ?"

Perceba que alguns agendamentos são complicados para expressar com uma simples trigger - por exemplo a cada 5 minutos entre 9 am e 10 am e a cada 20 minutos entre 1 pm e 10 pm. A solução para este cenário é simplificar criando duas triggers e registrar ambas para rodarem o mesmo job.

Feedback

Por favor, dê a sua opinião a respeito do conteúdo apresentado. Correções, sugestões ou qualquer outra informação relevante é de suma importância para nós e será muito bem vinda.

Contato: owner@hotwork.dev.java.net