Pianificazione in primavera con Quartz

1. Panoramica

In questo tutorial costruiremo un semplice Scheduler in primavera con Quartz .

Inizieremo con un semplice obiettivo in mente: configurare facilmente un nuovo lavoro pianificato.

1.1. Componenti chiave dell'API Quartz

Quartz ha un'architettura modulare. Consiste di diversi componenti di base che possono essere combinati secondo necessità. In questo tutorial, ci concentreremo su quelli comuni a ogni lavoro: Job , JobDetail , Trigger e Scheduler .

Sebbene utilizzeremo Spring per gestire l'applicazione, ogni singolo componente può essere configurato in due modi: il modo Quartz o il modo Spring (utilizzando le sue classi di convenienza).

Copriremo entrambi per quanto possibile per completezza, ma entrambi possono essere adottati. Cominciamo a costruire, un componente alla volta.

2. Lavoro e JobDetail

2.1. Lavoro

L'API fornisce un'interfaccia di lavoro con un solo metodo: eseguire. Deve essere implementato dalla classe che contiene il lavoro effettivo da svolgere, ovvero l'attività. Quando il trigger di un lavoro viene attivato, lo scheduler richiama il metodo di esecuzione , passandogli un oggetto JobExecutionContext .

Il JobExecutionContext prevede l'istanza di lavoro con informazioni sul suo ambiente di runtime, tra cui una maniglia per lo scheduler, una maniglia per il grilletto, e del lavoro JobDetail oggetto.

In questo rapido esempio, il lavoro delega l'attività a una classe di servizio:

@Component public class SampleJob implements Job { @Autowired private SampleJobService jobService; public void execute(JobExecutionContext context) throws JobExecutionException { jobService.executeSampleJob(); } } 

2.2. JobDetail

Sebbene il lavoro sia il cavallo di battaglia, Quartz non memorizza un'istanza effettiva della classe di lavoro. Possiamo invece definire un'istanza del lavoro utilizzando la classe JobDetail . La classe del lavoro deve essere fornita a JobDetail in modo che conosca il tipo di lavoro da eseguire.

2.3. Quartz JobBuilder

Quartz JobBuilder fornisce un'API in stile builder per la costruzione di entità JobDetail .

@Bean public JobDetail jobDetail() { return JobBuilder.newJob().ofType(SampleJob.class) .storeDurably() .withIdentity("Qrtz_Job_Detail") .withDescription("Invoke Sample Job service...") .build(); }

2.4. Spring JobDetailFactoryBean

JobDetailFactoryBean di Spring fornisce un utilizzo in stile bean per la configurazione delle istanze di JobDetail . Utilizza il nome del bean Spring come nome del lavoro, se non diversamente specificato:

@Bean public JobDetailFactoryBean jobDetail() { JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); jobDetailFactory.setJobClass(SampleJob.class); jobDetailFactory.setDescription("Invoke Sample Job service..."); jobDetailFactory.setDurability(true); return jobDetailFactory; }

Una nuova istanza di JobDetail viene creata per ogni esecuzione del lavoro. L' oggetto JobDetail trasmette le proprietà dettagliate del lavoro. Una volta completata l'esecuzione, i riferimenti all'istanza vengono eliminati.

3. Trigger

Un Trigger è il meccanismo per pianificare un Job , cioè un'istanza Trigger “attiva” l'esecuzione di un Job. Esiste una chiara separazione delle responsabilità tra il lavoro (nozione di attività) e il trigger (meccanismo di pianificazione).

Oltre a Job , il trigger necessita anche di un tipo che può essere scelto in base ai requisiti di pianificazione.

Diciamo, vogliamo programmare la nostra attività in modo che venga eseguita una volta ogni ora, indefinitamente: possiamo usare TriggerBuilder di Quartz o SimpleTriggerFactoryBean di Spring per farlo.

3.1. Quartz TriggerBuilder

TriggerBuilder è un'API in stile builder per la costruzione dell'entità Trigger :

@Bean public Trigger trigger(JobDetail job) { return TriggerBuilder.newTrigger().forJob(job) .withIdentity("Qrtz_Trigger") .withDescription("Sample trigger") .withSchedule(simpleSchedule().repeatForever().withIntervalInHours(1)) .build(); }

3.2. Spring SimpleTriggerFactoryBean

SimpleTriggerFactoryBean fornisce l'utilizzo in stile bean per la configurazione di SimpleTrigger . Utilizza il nome del bean Spring come nome del trigger e il valore predefinito è ripetizione indefinita, se non diversamente specificato:

@Bean public SimpleTriggerFactoryBean trigger(JobDetail job) { SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); trigger.setJobDetail(job); trigger.setRepeatInterval(3600000); trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); return trigger; }

4. Configurazione di JobStore

JobStore fornisce il meccanismo di archiviazione per il lavoro e il trigger ed è responsabile del mantenimento di tutti i dati rilevanti per il pianificatore di lavori. L'API supporta archivi in memoria e persistenti .

4.1. JobStore in memoria

Ad esempio, utilizzeremo il RAMJobStore in memoria che offre prestazioni incredibilmente veloci e configurazione semplice tramite quartz.properties :

org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore

L'ovvio svantaggio di RAMJobStore è che è di natura volatile . Tutte le informazioni di pianificazione vengono perse tra gli arresti. Se le definizioni e le pianificazioni del lavoro devono essere mantenute tra gli arresti, è necessario utilizzare JDBCJobStore persistente .

Per abilitare un JobStore in memoria in primavera , impostiamo questa proprietà nella nostra application.properties :

spring.quartz.job-store-type=memory

4.2. JDBC JobStore

There are two types of JDBCJobStore: JobStoreTX and JobStoreCMT. They both do the same job of storing scheduling information in a database.

The difference between the two is how they manage the transactions that commit the data. The JobStoreCMT type requires an application transaction to store data, whereas the JobStoreTX type starts and manages its own transactions.

There are several properties to set for a JDBCJobStore. At a minimum, we must specify the type of JDBCJobStore, the data source, and the database driver class. There are driver classes for most databases, but StdJDBCDelegate covers most cases:

org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.dataSource=quartzDataSource

Setting up a JDBC JobStore in Spring takes a few steps. Firstly, we set the store type in our application.properties:

spring.quartz.job-store-type=jdbc

Next, we need to enable auto-configuration and give Spring the data source needed by the Quartz scheduler. The @QuartzDataSource annotation does the hard work in configuring and initializing the Quartz database for us:

@Configuration @EnableAutoConfiguration public class SpringQrtzScheduler { @Bean @QuartzDataSource public DataSource quartzDataSource() { return DataSourceBuilder.create().build(); } }

5. Scheduler

The Scheduler interface is the main API for interfacing with the job scheduler.

A Scheduler can be instantiated with a SchedulerFactory. Once created, Jobs and Triggers can be registered with it. Initially, the Scheduler is in “stand-by” mode, and its start method must be invoked to start the threads that fire the execution of jobs.

5.1. Quartz StdSchedulerFactory

By simply invoking the getScheduler method on the StdSchedulerFactory, we can instantiate the Scheduler, initialize it (with the configured JobStore and ThreadPool), and return a handle to its API:

@Bean public Scheduler scheduler(Trigger trigger, JobDetail job, SchedulerFactoryBean factory) throws SchedulerException { Scheduler scheduler = factory.getScheduler(); scheduler.scheduleJob(job, trigger); scheduler.start(); return scheduler; }

5.2. Spring SchedulerFactoryBean

Spring's SchedulerFactoryBean provides bean-style usage for configuring a Scheduler, manages its life-cycle within the application context, and exposes the Scheduler as a bean for dependency injection:

@Bean public SchedulerFactoryBean scheduler(Trigger trigger, JobDetail job, DataSource quartzDataSource) { SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean(); schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties")); schedulerFactory.setJobFactory(springBeanJobFactory()); schedulerFactory.setJobDetails(job); schedulerFactory.setTriggers(trigger); schedulerFactory.setDataSource(quartzDataSource); return schedulerFactory; }

5.3. Configuring SpringBeanJobFactory

The SpringBeanJobFactory provides support for injecting the scheduler context, job data map, and trigger data entries as properties into the job bean while creating an instance.

However, it lacks support for injecting bean references from the application context. Thanks to the author of this blog post, we can add auto-wiring support to SpringBeanJobFactory like so:

@Bean public SpringBeanJobFactory springBeanJobFactory() { AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory(); jobFactory.setApplicationContext(applicationContext); return jobFactory; }

6. Conclusion

That's all. We have just built our first basic scheduler using the Quartz API as well as Spring's convenience classes.

The key takeaway from this tutorial is that we were able to configure a job with just a few lines of code and without using any XML-based configuration.

The complete source code for the example is available in this github project. It is a Maven project which can be imported and run as-is. The default setting uses Spring's convenience classes, which can be easily switched to Quartz API with a run-time parameter (refer to the README.md in the repository).