Arquitetura e Componentes Nucleares
O Apache Quartz opera mediante a coordenação de objetos acoplados dinamicamente. Para dominar sua implementação, é necessário compreender a função de cada peça no fluxo de execução:
- Tarefa (Job): Interface padrão que obriga a implementação do método
execute(JobExecutionContext contexto). Nesse ponto reside a lógica operacional a ser executada conforme a periodicidade definida. - Metadados da Tarefa (JobDetail): Como o motor não reaproveita instâncias de
Jobentre disparos, utiliza-se esta classe para descrever configurações imutáveis: nome, grupo, classe concreta e um mapa de atributos (JobDataMap) injetáveis. - Gatilho (Trigger): Define a regra temporal. As implementações
SimpleTriggercobrem intervalos fixos ou execuções pontuais, enquantoCronTriggerpermite sintaxes avançadas baseadas em cron. - Calendário de Restrições (Calendar): Agrupa datas específicas para filtragem. Diferente do
java.util.Calendar, esta classe do Quartz atua como negacionista de janelas (ex.: excluir feriados corporativos ou finais de semana). - Agendador (Scheduler): Motor central responsável por associar gatilhos a tarefas, gerenciar estados e expor operações de ciclo de vida (
start(),pause(),shutdown()). Possui contexto global acessível viaSchedulerContext. - Pool de Threads: Conjunto de linhas assíncronas pré-alocadas. Garante que tarefas concurrentes sejam atendidas sem sobrecarga de criação de processos.
Diferença entre Jobs com Estado: Jobs StatelessJob (padrão) isolam dados por execução. Jobs marcados com StatefulJob compartilham JobDataMap, persistindo alterações entre rodadas e bloqueando concorrência até o término. Priorize sempre o modelo stateless para evitar deadlocks internos.
Estrutura e Sintaxe Cron
As expressões temporiais seguem seis campos obrigatórios, sendo o ano opcional:
| Campo | Valores Válidos | Caracteres Especiais |
|---|---|---|
| Segundo | 0-59 | ,, -, *, / |
| Minuto | 0-59 | ,, -, *, / |
| Hora | 0-23 | ,, -, *, / |
| Dia do Mês | 1-31 | ,, -, *, ?, L, W |
| Mês | 1-12 / JAN-DEZ | ,, -, *, / |
| Dia da Semana | 1-7 / DOM-SÁB | ,, -, *, ?, L, # |
Operadores Principais:
?: Ausência de especificação (usado em dias quando se restringe mês ou semana)./: Incremento.0/15significa começar em 0 e somar 15.L: Último dia/semana do período corrente.W: Dia útil (segunda a sexta) mais próximo da data informada.#: Posição ordinal.2#3equivale ao terceiro segunda-feira do mês.
Exemplos Práticos:
- A cada 5 segundos:
0/5 * * * * ? - Domingos às 02h00:
0 0 2 ? * SUN - Último dia do mês à meia-noite:
0 0 0 L * ? - Dias úteis entre 09h e 17h:
0 0 9-17 ? * MON-FRI
Implementação Padrão em Java
Inclua a bbilioteca oficial no manifesto:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
Declare a classe executável:
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Map;
public class ProcessadorBatches implements Job {
@Override
public void execute(JobExecutionContext ctx) throws JobExecutionException {
String identificador = ctx.getJobDetail().getKey().getName();
Map<String, Object> params = ctx.getJobDetail().getJobDataMap();
System.out.printf("[TASK %s] Executando lógica de processamento...%n", identificador);
// Operações de negócio aqui
}
}
Orquestre a primeira rodada em memória:
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class OrchestradorInical {
public static void main(String[] args) {
try {
Scheduler motor = StdSchedulerFactory.getDefaultScheduler();
JobDefinition job = JobBuilder.newJob(ProcessadorBatches.class)
.withIdentity("batch_principal", "financeiro")
.usingJobData("modo", "producao")
.build();
Trigger disparador = TriggerBuilder.newTrigger()
.withIdentity("disp_horario", "financeiro")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
.build();
motor.scheduleJob(job, disparador);
motor.start();
} catch (SchedulerException falha) {
throw new RuntimeException("Erro crítico ao inicializar schedulers", falha);
}
}
}
Gerenciamento Dinâmico de Cronogramas
Para cenários administrativos que requerem mutação runtime de schedules, encapsule as APIs nativas:
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.List;
public class GestorDeRotinas {
private final Scheduler engine;
public GestorDeRotinas() throws SchedulerException {
this.engine = StdSchedulerFactory.getDefaultScheduler();
}
public void inscrever(ConfigTarefa conf) throws SchedulerException {
if (!engine.isStarted()) engine.start();
JobDefinition job = JobBuilder.newJob(conf.tipo())
.withIdentity(conf.nome(), conf.grupoJob())
.build();
Trigger gatilho = TriggerBuilder.newTrigger()
.withIdentity(conf.trigger(), conf.grupoTrigger())
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(conf.regra()))
.build();
engine.scheduleJob(job, gatilho);
}
public void resincronizar(String nomeTask, String grupoTask, String novaRegra) throws SchedulerException {
TriggerKey referencia = TriggerKey.triggerKey("trigger_" + nomeTask, "gt_padrao");
CronTrigger atual = (CronTrigger) engine.getTrigger(referencia);
if (atual == null) return;
Trigger modificado = TriggerBuilder.newTrigger()
.withIdentity(referencia.getName(), referencia.getGroup())
.withSchedule(CronScheduleBuilder.cronSchedule(novaRegra))
.build();
engine.rescheduleJob(referencia, modificado);
}
public void descartar(String nomeTask, String grupoTask, String triggerNome, String grupoTrig) throws SchedulerException {
TriggerKey tChave = TriggerKey.triggerKey(triggerNome, grupoTrig);
engine.pauseTrigger(tChave);
engine.unscheduleJob(tChave);
engine.deleteJob(JobKey.jobKey(nomeTask, grupoTask));
}
public void interromper() throws SchedulerException {
if (!engine.isShutdown()) engine.shutdown(false);
}
}
Teste de fluxo alterado:
public class SimulacaoRuntime {
public static void main(String[] args) throws Exception {
var gerenciador = new GestorDeRotinas();
var spec = new RecordConfig("alerta_servidor", "infra", "alerta_t", "infra_gt",
ProcessadorBatches.class, "0/3 * * * * ?");
System.out.println("Inscrevendo rotina base...");
gerenciador.inscrever(spec);
Thread.sleep(9000);
System.out.println("Alterando cron para 10s...");
gerenciador.resincronizar("alerta_servidor", "infra", "0/10 * * * * ?");
Thread.sleep(20000);
System.out.println("Removendo inscrição...");
gerenciador.descartar("alerta_servidor", "infra", "alerta_t", "infra_gt");
gerenciador.interromper();
}
}
record RecordConfig(String nome, String grupoJob, String trigger, String grupoTrigger, Class<?> tipo, String regra) {}
Adaptação ao Container Spring
O ecossistema Spring delega a lifecycle management via XML/Annotations. Configure manualmente:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.30</version>
</dependency>
Arquivo quartz-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Bind da tarefa ao contexto -->
<bean id="executerInterno" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="rotina_diaria"/>
<property name="group" value="jobs_pool"/>
<property name="jobClass" value="br.com.projeto.jobs.ProcessadorBatches"/>
<property name="durability" value="true"/>
<property name="applicationContextJobDataKey" value="ctxApp"/>
</bean>
<!-- Trigger Cron vinculado -->
<bean id="trigger_noturno" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="name" value="trigger_dia"/>
<property name="group" value="cron_gpus"/>
<property name="jobDetail" ref="executerInterno"/>
<property name="cronExpression" value="0 0 02 * * ?"/>
</bean>
<!-- Factory mestre -->
<bean id="schedulerMotor" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list><ref bean="trigger_noturno"/></list>
</property>
<property name="autoStartup" value="false"/>
</bean>
</beans>
Validação pontual:
import org.quartz.Scheduler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class VerificadorSpring {
public static void main(String[] args) throws Exception {
ApplicationContext container = new ClassPathXmlApplicationContext("quartz-config.xml");
Scheduler plataforma = (Scheduler) container.getBean("schedulerMotor");
plataforma.start();
System.in.read(); // Bloqueio mantenedor de daemon
}
}