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