在?Spring?Boot?中使用?Quartz?調度作業(yè)的示例詳解
在本文中,我們將看看如何使用Quartz框架來調度任務。Quartz是Java應用程序調度庫的事實標準。Quartz支持在特定時間運行作業(yè)、重復作業(yè)執(zhí)行、將作業(yè)存儲在數據庫中以及Spring集成。
用于調度的Spring注解
在 Spring 應用程序中使用 Quartz 最簡單的方法是使用@Scheduled注解。接下來,我們將考慮一個 Spring Boot 應用程序的示例。讓我們添加必要的依賴項build.gradle
implementation 'org.springframework.boot:spring-boot-starter-quartz'
并考慮一個例子
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());
}
}此外,@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);
}
}結果將是每五秒在控制臺中輸出一個文本。
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
...
@Scheduled注解支持以下參數:
- fixedRate- 允許您以指定的固定間隔運行任務。
- fixedDelay- 在最后一次調用完成和下一次調用開始之間以固定延遲執(zhí)行任務。
- initialDelay- 該參數用于fixedRate并fixedDelay在第一次執(zhí)行具有指定延遲的任務之前等待。
- cron- 使用 cron-string 設置任務執(zhí)行計劃。還支持宏@yearly(or @annually)、@monthly、@weekly、@daily(or @midnight) 和@hourly.
默認情況下fixedRate,fixedDelay和initialDelay以毫秒為單位設置。這可以使用timeUnit參數進行更改,將值設置NANOSECONDS為DAYS。
此外,您可以在 @Scheduled 注釋中使用屬性:
application.properties
cron-string=0/5 * * * * ?
PeriodicTask.java
@Component
public class PeriodicTask {
@Scheduled(cron = "${cron-string}")
public void everyFiveSeconds() {
System.out.println("Periodic task: " + new Date());
}
}要使用屬性,您可以使用fixedRateString, fixedDelayString, 和initialDelayString參數來代替 fixedRate,fixedDelay和initialDelay相應的。
使用Quartz
在前面的例子中,我們執(zhí)行了定時任務,但同時我們不能動態(tài)設置作業(yè)的開始時間,也不能給它傳遞參數。要解決這些問題,可以直接使用 Quartz。
下面列出了主要的 Quartz 接口:
- Job是由包含我們希望執(zhí)行的業(yè)務邏輯的類實現的接口
- JobDetails定義Job與之相關的實例和數據
- Trigger描述作業(yè)執(zhí)行的時間表
- Scheduler是主要的 Quartz 界面,為作業(yè)和觸發(fā)器提供所有操作和搜索操作
要直接使用 Quartz,無需使用@EnableScheduling注解定義配置org.springframework.boot:spring-boot-starter-quartz,您可以使用org.quartz-scheduler:quartz.
讓我們定義 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()));
}
}要實現Job接口,您只需要實現一個execute接受JobExecutionContext類型參數的方法。JobExecutionContext包含有關作業(yè)實例、觸發(fā)器、調度程序的信息以及有關作業(yè)執(zhí)行的其他信息。
現在讓我們定義一個作業(yè)實例:
JobDetail job = JobBuilder.newJob(SimpleJob.class).build();
并創(chuàng)建一個將在五秒后觸發(fā)的觸發(fā)器:
Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5)
.atZone(ZoneId.systemDefault()).toInstant());
Trigger trigger = TriggerBuilder.newTrigger()
.startAt(afterFiveSeconds)
.build();另外,創(chuàng)建一個調度程序:
SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler();
在“待機”模式下Scheduler初始化,所以我們必須調用start方法:
scheduler.start();
現在我們可以安排作業(yè)的執(zhí)行:
scheduler.scheduleJob(job, trigger);
更進一步,在創(chuàng)建時JobDetails,讓我們添加額外的數據并在執(zhí)行作業(yè)時使用它:
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));
}
}需要注意的是,添加到的所有值都JobDataMap必須是可序列化的。
工作商店
Quartz 將有關JobDetail、Trigger的數據和其他信息存儲在JobStore. 默認情況下,JobStore使用內存。這意味著如果我們在它們被觸發(fā)之前已經安排了任務并關閉了應用程序(例如,在重新啟動或崩潰時),那么它們將永遠不會再次執(zhí)行。Quartz 還支持 JDBC-JobStore 在數據庫中存儲信息。
在使用 JDBC-JobStore 之前,需要在 Quartz 將使用的數據庫中創(chuàng)建表。默認情況下,這些表的前綴為QRTZ_.
Quartz 源代碼包含用于為各種數據庫(如 Oracle、Postgres、MS SQL Server、MySQL 等)創(chuàng)建表的SQL 腳本,并且還有一個用于 Liquibase 的現成 XML 文件。
spring.quartz.jdbc.initialize-schema=always此外,通過指定屬性,可以在啟動應用程序時自動創(chuàng)建 QRTZ 表。
為簡單起見,我們將使用第二種方法和 H2 數據庫。讓我們配置一個數據源,使用 JDBCJobStore 并在 application.properties 中創(chuàng)建 QRTZ 表:
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
要考慮這些設置,必須將調度程序創(chuàng)建為 Spring bean:
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);
};
}
}線程池配置
Quartz 在單獨的線程中運行每個任務,您可以為調度程序配置線程池。還需要注意的是,默認情況下,通過@Scheduled注解和直接通過 Quartz 啟動的任務是在不同的線程池中啟動的。我們可以確保這一點:
PeriodicTask.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()));
}
}輸出將是:
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
任務的線程池@Scheduled只包含一個線程。
讓我們更改 @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);
}
}輸出現在將是這樣的:
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
如您所見,這些設置僅影響使用注釋設置的任務。
現在讓我們更改直接使用 Quartz 的調度程序的設置。這可以通過兩種方式完成:通過屬性文件或通過創(chuàng)建 bean SchedulerFactoryBeanCustomizer。
讓我們使用第一種方法。如果我們沒有通過 Spring 初始化 Quartz,我們將不得不在 quartz.properties 文件中注冊屬性。在我們的例子中,我們需要在 application.properties 中注冊屬性,并spring.quartz.properties.為其添加前綴。
application.properties
spring.quartz.properties.org.quartz.threadPool.threadNamePrefix=my-scheduler_Worker spring.quartz.properties.org.quartz.threadPool.threadCount=25
讓我們啟動應用程序。現在輸出將是這樣的:
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
現在調用啟動任務的線程my-scheduler_Worker-1。
多個調度器
如果您需要創(chuàng)建多個具有不同參數的調度程序,則必須定義多個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);
};
}
}輸出:
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
結論
Quartz 是一個用于自動執(zhí)行計劃任務的強大框架。它既可以在簡單直觀的 Spring 注解的幫助下使用,也可以通過精細的定制和調整來使用,從而為復雜的問題提供解決方案。
到此這篇關于在SpringBoot中使用Quartz調度作業(yè)的示例詳解的文章就介紹到這了,更多相關SpringBoot調度作業(yè)內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java的外部類為什么不能使用private和protected進行修飾的講解
今天小編就為大家分享一篇關于Java的外部類為什么不能使用private和protected進行修飾的講解,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-04-04
Spring Cloud Nacos 和 Eureka區(qū)別解析
Spring Cloud Nacos 和 Spring Cloud Eureka 都是 Spring Cloud 微服務框架中的服務注冊和發(fā)現組件,用于幫助開發(fā)者輕松地構建和管理微服務應用,這篇文章主要介紹了Spring Cloud Nacos 和 Eureka區(qū)別,需要的朋友可以參考下2023-08-08
Spring Boot利用Docker快速部署項目的完整步驟
這篇文章主要給大家介紹了關于Spring Boot利用Docker快速部署項目的完整步驟,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Spring Boot具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-07-07

