En este artículo, veremos cómo programar tareas utilizando el . Quartz es el estándar de facto de programación de bibliotecas para aplicaciones Java. Quartz admite la ejecución de trabajos en un momento determinado, la repetición de ejecuciones de trabajos, el almacenamiento de trabajos en una base de datos y la integración con Spring. marco de cuarzo Anotaciones de primavera para la programación La forma más fácil de usar las aplicaciones de Quartz en Spring es usar la anotación . A continuación, consideraremos un ejemplo de una aplicación Spring Boot. Agreguemos la dependencia necesaria en @Scheduled build.gradle implementation 'org.springframework.boot:spring-boot-starter-quartz' y consideremos un ejemplo package quartzdemo.tasks; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.Date; @Component public class PeriodicTask { @Scheduled(cron = "0/5 * * * * ?") public void everyFiveSeconds() { System.out.println("Periodic task: " + new Date()); } } Además, para que la anotación funcione, debe agregar una configuración con la anotación . @Scheduled @EnableScheduling package quartzdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling public class QuartzDemoApplication { public static void main(String[] args) { SpringApplication.run(QuartzDemoApplication.class, args); } } El resultado será una salida de texto en la consola cada cinco segundos. Periodic task: Thu Jul 07 18:24:50 EDT 2022 Periodic task: Thu Jul 07 18:24:55 EDT 2022 Periodic task: Thu Jul 07 18:25:00 EDT 2022 ... La anotación admite los siguientes parámetros: @Scheduled : le permite ejecutar una tarea en un intervalo fijo especificado. fixedRate : ejecuta una tarea con un retraso fijo entre la finalización de la última invocación y el inicio de la siguiente. fixedDelay : el parámetro se usa con y para esperar antes de la primera ejecución de la tarea con el retraso especificado. initialDelay fixedRate fixedDelay : establezca el cronograma de ejecución de la tarea usando la cadena cron. También admite macros (o ), , , (o ) y . cron @yearly @annually @monthly @weekly @daily @midnight @hourly De forma predeterminada, , e se establecen en milisegundos. Esto se puede cambiar usando el parámetro , configurando el valor de a . fixedRate fixedDelay initialDelay timeUnit NANOSECONDS DAYS Además, puede usar propiedades en la anotación @Scheduled: aplicación.propiedades cron-string=0/5 * * * * ? TareaPeriódica.java @Component public class PeriodicTask { @Scheduled(cron = "${cron-string}") public void everyFiveSeconds() { System.out.println("Periodic task: " + new Date()); } } Para usar propiedades, puede utilizar , e en lugar de , e según corresponda. fixedRateString fixedDelayString initialDelayString fixedRate fixedDelay initialDelay Usar cuarzo directamente En el ejemplo anterior, ejecutamos tareas programadas, pero al mismo tiempo, no pudimos establecer dinámicamente la hora de inicio del trabajo ni pasarle parámetros. Para resolver estos problemas, puedes usar directamente Quartz. Las principales interfaces de Quartz se enumeran a continuación: es una interfaz a ser implementada por las clases que contienen la lógica de negocio que deseamos que se ejecute Job define instancias de y datos relacionados con él JobDetails Job describe la programación de la ejecución del trabajo. Trigger es la interfaz principal de Quartz que proporciona todas las operaciones de manipulación y búsqueda de trabajos y disparadores. Scheduler Para trabajar con Quartz directamente, no es necesario definir una configuración con la anotación , y en lugar de la , puede usar . @EnableScheduling org.springframework.boot:spring-boot-starter-quartz org.quartz-scheduler:quartz Definamos la clase SimpleJob: package quartzdemo.jobs; import org.quartz.Job; import org.quartz.JobExecutionContext; import java.text.MessageFormat; public class SimpleJob implements Job { @Override public void execute(JobExecutionContext context) { System.out.println(MessageFormat.format("Job: {0}", getClass())); } } Para implementar la interfaz de , debe implementar solo un método de que acepte un parámetro del tipo . contiene información sobre la instancia del trabajo, el activador, el programador y otra información sobre la ejecución del trabajo. Job execute JobExecutionContext JobExecutionContext Ahora definamos una instancia del trabajo: JobDetail job = JobBuilder.newJob(SimpleJob.class).build(); Y cree un activador que se activará después de cinco segundos: Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5) .atZone(ZoneId.systemDefault()).toInstant()); Trigger trigger = TriggerBuilder.newTrigger() .startAt(afterFiveSeconds) .build(); Además, cree un programador: SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); El se inicializa en modo "en espera", por lo que debemos invocar el método de : Scheduler start scheduler.start(); Ahora podemos programar la ejecución del trabajo: scheduler.scheduleJob(job, trigger); Además, al crear , agreguemos datos adicionales y usémoslos al ejecutar un trabajo: JobDetails QuartzDemoApplication.java @SpringBootApplication public class QuartzDemoApplication { public static void main(String[] args) { SpringApplication.run(QuartzDemoApplication.class, args); onStartup(); } private static void onStartup() throws SchedulerException { JobDetail job = JobBuilder.newJob(SimpleJob.class) .usingJobData("param", "value") // add a parameter .build(); Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5) .atZone(ZoneId.systemDefault()).toInstant()); Trigger trigger = TriggerBuilder.newTrigger() .startAt(afterFiveSeconds) .build(); SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); scheduler.start(); scheduler.scheduleJob(job, trigger); } } SimpleJob.java public class SimpleJob implements Job { @Override public void execute(JobExecutionContext context) { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); String param = dataMap.getString("param"); System.out.println(MessageFormat.format("Job: {0}; Param: {1}", getClass(), param)); } } Es importante tener en cuenta que todos los valores que se agregan a deben ser serializables. JobDataMap Tienda de trabajos Quartz almacena datos sobre , y otra información en . De forma predeterminada, se utiliza en memoria. Esto significa que si tenemos tareas programadas y cerramos la aplicación (por ejemplo, al reiniciar o fallar) antes de que se disparen, entonces nunca más se ejecutarán. Quartz también es compatible con JDBC-JobStore para almacenar información en una base de datos. JobDetail Trigger JobStore JobStore Antes de utilizar JDBC-JobStore, es necesario crear tablas en la base de datos que utilizará Quartz. De forma predeterminada, estas tablas tienen el prefijo . QRTZ_ El código fuente de Quartz contiene secuencias de para crear tablas para varias bases de datos, como Oracle, Postgres, MS SQL Server, MySQL y otras, y también tiene un archivo XML listo para usar para Liquibase. comandos SQL Además, las tablas QRTZ se pueden crear automáticamente al iniciar la aplicación especificando la . spring.quartz.jdbc.initialize-schema=always Para simplificar, usaremos el segundo método y la base de datos H2. Configuremos una fuente de datos, usemos JDBCJobStore y creemos tablas QRTZ en application.properties: spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.quartz.job-store-type=jdbc spring.quartz.jdbc.initialize-schema=always Para que se tengan en cuenta estas configuraciones, el Programador debe crearse como un bean Spring: package quartzdemo; import org.quartz.*; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import quartzdemo.jobs.SimpleJob; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; @SpringBootApplication @EnableScheduling public class QuartzDemoApplication { public static void main(String[] args) { SpringApplication.run(QuartzDemoApplication.class, args); } @Bean() public Scheduler scheduler(SchedulerFactoryBean factory) throws SchedulerException { Scheduler scheduler = factory.getScheduler(); scheduler.start(); return scheduler; } @Bean public CommandLineRunner run(Scheduler scheduler) { return (String[] args) -> { JobDetail job = JobBuilder.newJob(SimpleJob.class) .usingJobData("param", "value") // add a parameter .build(); Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5) .atZone(ZoneId.systemDefault()).toInstant()); Trigger trigger = TriggerBuilder.newTrigger() .startAt(afterFiveSeconds) .build(); scheduler.scheduleJob(job, trigger); }; } } Configuración del grupo de subprocesos Quartz ejecuta cada tarea en un subproceso separado y puede configurar un grupo de subprocesos para programadores. También se debe tener en cuenta que, de forma predeterminada, las tareas iniciadas a través de la anotación y directamente a través de Quartz se inician en diferentes grupos de subprocesos. Podemos asegurarnos de esto: @Scheduled TareaPeriódica.java @Component public class PeriodicTask { @Scheduled(cron = "${cron-string}") public void everyFiveSeconds() { System.out.println(MessageFormat.format("Periodic task: {0}; Thread: {1}", new Date().toString(), Thread.currentThread().getName())); } } SimpleJob.java public class SimpleJob implements Job { @Override public void execute(JobExecutionContext context) { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); String param = dataMap.getString("param"); System.out.println(MessageFormat.format("Job: {0}; Param: {1}; Thread: {2}", getClass(), param, Thread.currentThread().getName())); } } la salida será: Periodic task: Thu Jul 07 19:22:45 EDT 2022; Thread: scheduling-1 Job: class quartzdemo.jobs.SimpleJob; Param: value; Thread: quartzScheduler_Worker-1 Periodic task: Thu Jul 07 19:22:50 EDT 2022; Thread: scheduling-1 Periodic task: Thu Jul 07 19:22:55 EDT 2022; Thread: scheduling-1 Periodic task: Thu Jul 07 19:23:00 EDT 2022; Thread: scheduling-1 El grupo de subprocesos para las tareas contiene solo un subproceso. @Scheduled Cambiemos la configuración del programador para las tareas @Scheduled: package quartzdemo; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.config.ScheduledTaskRegistrar; @Configuration public class SchedulingConfiguration implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(10); threadPoolTaskScheduler.setThreadNamePrefix("my-scheduled-task-pool-"); threadPoolTaskScheduler.initialize(); taskRegistrar.setTaskScheduler(threadPoolTaskScheduler); } } la salida ahora será así: Periodic task: Thu Jul 07 19:44:10 EDT 2022; Thread: my-scheduled-task-pool-1 Job: class quartzdemo.jobs.SimpleJob; Param: value; Thread: quartzScheduler_Worker-1 Periodic task: Thu Jul 07 19:44:15 EDT 2022; Thread: my-scheduled-task-pool-1 Periodic task: Thu Jul 07 19:44:20 EDT 2022; Thread: my-scheduled-task-pool-2 Como puede ver, esta configuración afectó solo a las tareas establecidas mediante anotaciones. Ahora cambiemos la configuración del programador con el que trabajamos directamente con Quartz. Esto se puede hacer de dos maneras: a través del archivo de propiedades o creando un bean . SchedulerFactoryBeanCustomizer Usemos el primer método. Si no hubiéramos inicializado Quartz a través de Spring, tendríamos que registrar propiedades en el archivo quartz.properties. En nuestro caso, necesitamos registrar propiedades en application.properties, agregando el prefijo a ellos spring.quartz.properties. application.properties spring.quartz.properties.org.quartz.threadPool.threadNamePrefix=my-scheduler_Worker spring.quartz.properties.org.quartz.threadPool.threadCount=25 Iniciemos la aplicación. Ahora la salida será así: Periodic task: Sat Jul 23 10:45:55 MSK 2022; Thread: my-scheduled-task-pool-1 Job: class quartzdemo.jobs.SimpleJob; Param: value; Thread: my-scheduler_Worker-1 Ahora el hilo en el que se inicia la tarea se llama . my-scheduler_Worker-1 Múltiples programadores Si necesita crear varios programadores con diferentes parámetros, debe definir varios . Veamos un ejemplo. SchedulerFactoryBeans package quartzdemo; import quartzdemo.jobs.SimpleJob; import org.quartz.*; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import javax.sql.DataSource; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; import java.util.Properties; @SpringBootApplication @EnableScheduling public class QuartzDemoApplication { public static void main(String[] args) { SpringApplication.run(QuartzDemoApplication.class, args); } @Bean("customSchedulerFactoryBean1") public SchedulerFactoryBean customSchedulerFactoryBean1(DataSource dataSource) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); Properties properties = new Properties(); properties.setProperty("org.quartz.threadPool.threadNamePrefix", "my-custom-scheduler1_Worker"); factory.setQuartzProperties(properties); factory.setDataSource(dataSource); return factory; } @Bean("customSchedulerFactoryBean2") public SchedulerFactoryBean customSchedulerFactoryBean2(DataSource dataSource) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); Properties properties = new Properties(); properties.setProperty("org.quartz.threadPool.threadNamePrefix", "my-custom-scheduler2_Worker"); factory.setQuartzProperties(properties); factory.setDataSource(dataSource); return factory; } @Bean("customScheduler1") public Scheduler customScheduler1(@Qualifier("customSchedulerFactoryBean1") SchedulerFactoryBean factory) throws SchedulerException { Scheduler scheduler = factory.getScheduler(); scheduler.start(); return scheduler; } @Bean("customScheduler2") public Scheduler customScheduler2(@Qualifier("customSchedulerFactoryBean2") SchedulerFactoryBean factory) throws SchedulerException { Scheduler scheduler = factory.getScheduler(); scheduler.start(); return scheduler; } @Bean public CommandLineRunner run(@Qualifier("customScheduler1") Scheduler customScheduler1, @Qualifier("customScheduler2") Scheduler customScheduler2) { return (String[] args) -> { Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5).atZone(ZoneId.systemDefault()).toInstant()); JobDetail jobDetail1 = JobBuilder.newJob(SimpleJob.class).usingJobData("param", "value1").build(); Trigger trigger1 = TriggerBuilder.newTrigger().startAt(afterFiveSeconds).build(); customScheduler1.scheduleJob(jobDetail1, trigger1); JobDetail jobDetail2 = JobBuilder.newJob(SimpleJob.class).usingJobData("param", "value2").build(); Trigger trigger2 = TriggerBuilder.newTrigger().startAt(afterFiveSeconds).build(); customScheduler2.scheduleJob(jobDetail2, trigger2); }; } } Producción: Job: class quartzdemo.jobs.SimpleJob; Param: value2; Thread: my-custom-scheduler2_Worker-1 Job: class quartzdemo.jobs.SimpleJob; Param: value1; Thread: my-custom-scheduler1_Worker-1 El código fuente completo está disponible en . GitHub Conclusión Quartz es un marco poderoso para automatizar tareas programadas. Se puede usar tanto con la ayuda de anotaciones Spring simples e intuitivas como con una personalización y ajuste finos, lo que brinda una solución a problemas complejos.