本文側重SpringBoot與Quartz的整合,Quartz的基本入門概念不清楚的小夥伴可以看看這篇文章:任務排程框架Quartz快速入門!
本篇要點
- 介紹SpringBoot與Quartz單機版整合。
- 介紹Quartz持久化儲存。
SpringBoot與Quartz單機版快速整合
學習完非Spring環境下Quartz的使用,再來看SpringBoot你會感到更加自如,因為SpringBoot無非是利用它自動配置的特性,將一些重要的Bean自動配置到環境中,我們直接開箱即用,關於Quartz的自動配置定義在QuartzAutoConfiguration中。
引入依賴
主要是spring-boot-starter-quartz
這個依賴,是SpringBoot與Quartz的整合。
<!-- 實現對 Spring MVC 的自動化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 實現對 Quartz 的自動化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
建立Job
為了演示兩種Trigger及兩種配置方式,我們建立兩個不同的Job。
@Slf4j
public class FirstJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String now = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
log.info("當前的時間: " + now);
}
}
@Slf4j
public class SecondJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String now = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
log.info("SecondJob執行, 當前的時間: " + now);
}
}
我們在建立Job的時候,可以實現Job介面,也可以繼承QuartzJobBean。
QuartzJobBean實現了Job,並且定義了公用的execute方法,子類可以繼承QuartzJobBean並實現executeInternal方法。
public abstract class QuartzJobBean implements Job {
/**
* This implementation applies the passed-in job data map as bean property
* values, and delegates to {@code executeInternal} afterwards.
* @see #executeInternal
*/
@Override
public final void execute(JobExecutionContext context) throws JobExecutionException {
try {
// 將當前物件包裝為BeanWrapper
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 設定屬性
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValues(context.getScheduler().getContext());
pvs.addPropertyValues(context.getMergedJobDataMap());
bw.setPropertyValues(pvs, true);
}
catch (SchedulerException ex) {
throw new JobExecutionException(ex);
}
// 子類實現該方法
executeInternal(context);
}
/**
* Execute the actual job. The job data map will already have been
* applied as bean property values by execute. The contract is
* exactly the same as for the standard Quartz execute method.
* @see #execute
*/
protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;
}
排程器Scheduler繫結
Scheduler繫結有兩種方式,一種是使用bena的自動配置,一種是Scheduler手動配置。
自動配置,這裡演示SimpleScheduleBuilder
@Configuration
public class QuartzConfig {
private static final String ID = "SUMMERDAY";
@Bean
public JobDetail jobDetail1() {
return JobBuilder.newJob(FirstJob.class)
.withIdentity(ID + " 01")
.storeDurably()
.build();
}
@Bean
public Trigger trigger1() {
// 簡單的排程計劃的構造器
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5) // 頻率
.repeatForever(); // 次數
return TriggerBuilder.newTrigger()
.forJob(jobDetail1())
.withIdentity(ID + " 01Trigger")
.withSchedule(scheduleBuilder)
.build();
}
}
手動配置,這裡演示CronScheduleBuilder
@Component
public class JobInit implements ApplicationRunner {
private static final String ID = "SUMMERDAY";
@Autowired
private Scheduler scheduler;
@Override
public void run(ApplicationArguments args) throws Exception {
JobDetail jobDetail = JobBuilder.newJob(FirstJob.class)
.withIdentity(ID + " 01")
.storeDurably()
.build();
CronScheduleBuilder scheduleBuilder =
CronScheduleBuilder.cronSchedule("0/5 * * * * ? *");
// 建立任務觸發器
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withIdentity(ID + " 01Trigger")
.withSchedule(scheduleBuilder)
.startNow() //立即執行一次任務
.build();
// 手動將觸發器與任務繫結到排程器內
scheduler.scheduleJob(jobDetail, trigger);
}
}
yml配置
spring:
# Quartz 的配置,對應 QuartzProperties 配置類
quartz:
job-store-type: memory # Job 儲存器型別。預設為 memory 表示記憶體,可選 jdbc 使用資料庫。
auto-startup: true # Quartz 是否自動啟動
startup-delay: 0 # 延遲 N 秒啟動
wait-for-jobs-to-complete-on-shutdown: true # 應用關閉時,是否等待定時任務執行完成。預設為 false ,建議設定為 true
overwrite-existing-jobs: false # 是否覆蓋已有 Job 的配置
properties: # 新增 Quartz Scheduler 附加屬性
org:
quartz:
threadPool:
threadCount: 25 # 執行緒池大小。預設為 10 。
threadPriority: 5 # 執行緒優先順序
class: org.quartz.simpl.SimpleThreadPool # 執行緒池型別
# jdbc: # 這裡暫時不說明,使用 JDBC 的 JobStore 的時候,才需要配置
SpringBoot自動配置中:spring.quartz
對應的配置項定義在QuartzProperties
中。
主啟動類
@SpringBootApplication
public class DemoSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(DemoSpringBootApplication.class, args);
}
}
測試
啟動程式,FirstJob每5s執行一次,SecondJob每10s執行一次。
[eduler_Worker-1] com.hyhwky.standalone.FirstJob : FirstJob執行, 當前的時間: 2020-12-26 16:54:00
[eduler_Worker-2] com.hyhwky.standalone.SecondJob : SecondJob執行, 當前的時間: 2020-12-26 16:54:00
[eduler_Worker-3] com.hyhwky.standalone.FirstJob : FirstJob執行, 當前的時間: 2020-12-26 16:54:05
[eduler_Worker-4] com.hyhwky.standalone.SecondJob : SecondJob執行, 當前的時間: 2020-12-26 16:54:10
[eduler_Worker-5] com.hyhwky.standalone.FirstJob : FirstJob執行, 當前的時間: 2020-12-26 16:54:10
[eduler_Worker-6] com.hyhwky.standalone.FirstJob : FirstJob執行, 當前的時間: 2020-12-26 16:54:15
[eduler_Worker-7] com.hyhwky.standalone.SecondJob : SecondJob執行, 當前的時間: 2020-12-26 16:54:20
Quartz持久化配置
Quartz持久化配置提供了兩種儲存器:
型別 | 優點 | 缺點 |
---|---|---|
RAMJobStore | 不要外部資料庫,配置容易,執行速度快 | 因為排程程式資訊是儲存在被分配給 JVM 的記憶體裡面,所以,當應用程式停止執行時,所有排程資訊將被丟失。另外因為儲存到JVM記憶體裡面,所以可以儲存多少個 Job 和 Trigger 將會受到限制 |
JDBC 作業儲存 | 支援叢集,因為所有的任務資訊都會儲存到資料庫中,可以控制事物,還有就是如果應用伺服器關閉或者重啟,任務資訊都不會丟失,並且可以恢復因伺服器關閉或者重啟而導致執行失敗的任務 | 執行速度的快慢取決與連線資料庫的快慢 |
建立資料庫表
為了測試Quartz的持久化配置,我們事先在mysql中建立一個資料庫quartz,並執行指令碼,指令碼藏在org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar!\org\quartz\impl\jdbcjobstore\tables_mysql_innodb.sql
,jdbcjobstore中有支援許多種資料庫的指令碼,可以按需執行。
mysql> use quartz;
Database changed
mysql> show tables;
+--------------------------+
| Tables_in_quartz |
+--------------------------+
| qrtz_blob_triggers |## blog型別儲存triggers
| qrtz_calendars |## 以blog型別儲存Calendar資訊
| qrtz_cron_triggers |## 儲存cron trigger資訊
| qrtz_fired_triggers |## 儲存已觸發的trigger相關資訊
| qrtz_job_details |## 儲存每一個已配置的job details
| qrtz_locks |## 儲存悲觀鎖的資訊
| qrtz_paused_trigger_grps |## 儲存已暫停的trigger組資訊
| qrtz_scheduler_state |## 儲存Scheduler狀態資訊
| qrtz_simple_triggers |## 儲存simple trigger資訊
| qrtz_simprop_triggers |## 儲存其他幾種trigger資訊
| qrtz_triggers |## 儲存已配置的trigger資訊
+--------------------------+
所有的表中都含有一個SCHED_NAME
欄位,對應我們配置的scheduler-name
,相同 Scheduler-name的節點,形成一個 Quartz 叢集。
引入mysql相關依賴
<dependencies>
<!-- 實現對 Spring MVC 的自動化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 實現對 Quartz 的自動化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
配置yml
spring:
datasource:
quartz:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/quartz?serverTimezone=GMT%2B8
username: root
password: 123456
quartz:
job-store-type: jdbc # 使用資料庫儲存
scheduler-name: hyhScheduler # 相同 Scheduler 名字的節點,形成一個 Quartz 叢集
wait-for-jobs-to-complete-on-shutdown: true # 應用關閉時,是否等待定時任務執行完成。預設為 false ,建議設定為 true
jdbc:
initialize-schema: never # 是否自動使用 SQL 初始化 Quartz 表結構。這裡設定成 never ,我們手動建立表結構。
properties:
org:
quartz:
# JobStore 相關配置
jobStore:
dataSource: quartzDataSource # 使用的資料來源
class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore 實現類
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: QRTZ_ # Quartz 表字首
isClustered: true # 是叢集模式
clusterCheckinInterval: 1000
useProperties: false
# 執行緒池相關配置
threadPool:
threadCount: 25 # 執行緒池大小。預設為 10 。
threadPriority: 5 # 執行緒優先順序
class: org.quartz.simpl.SimpleThreadPool # 執行緒池型別
配置資料來源
@Configuration
public class DataSourceConfiguration {
private static HikariDataSource createHikariDataSource(DataSourceProperties properties) {
// 建立 HikariDataSource 物件
HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
// 設定執行緒池名
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
/**
* 建立 quartz 資料來源的配置物件
*/
@Primary
@Bean(name = "quartzDataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.quartz")
// 讀取 spring.datasource.quartz 配置到 DataSourceProperties 物件
public DataSourceProperties quartzDataSourceProperties() {
return new DataSourceProperties();
}
/**
* 建立 quartz 資料來源
*/
@Bean(name = "quartzDataSource")
@ConfigurationProperties(prefix = "spring.datasource.quartz.hikari")
@QuartzDataSource
public DataSource quartzDataSource() {
// 獲得 DataSourceProperties 物件
DataSourceProperties properties = this.quartzDataSourceProperties();
// 建立 HikariDataSource 物件
return createHikariDataSource(properties);
}
}
建立任務
@Component
public class JobInit implements ApplicationRunner {
private static final String ID = "SUMMERDAY";
@Autowired
private Scheduler scheduler;
@Override
public void run(ApplicationArguments args) throws Exception {
JobDetail jobDetail = JobBuilder.newJob(SecondJob.class)
.withIdentity(ID + " 02")
.storeDurably()
.build();
CronScheduleBuilder scheduleBuilder =
CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");
// 建立任務觸發器
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withIdentity(ID + " 02Trigger")
.withSchedule(scheduleBuilder)
.startNow() //立即執行一次任務
.build();
Set<Trigger> set = new HashSet<>();
set.add(trigger);
// boolean replace 表示啟動時對資料庫中的quartz的任務進行覆蓋。
scheduler.scheduleJob(jobDetail, set, true);
}
}
啟動測試
啟動測試之後,我們的quartz任務相關資訊就已經成功儲存到mysql中了。
mysql> select * from qrtz_simple_triggers;
+--------------+---------------------+---------------+--------------+-----------------+-----------------+
| SCHED_NAME | TRIGGER_NAME | TRIGGER_GROUP | REPEAT_COUNT | REPEAT_INTERVAL | TIMES_TRIGGERED |
+--------------+---------------------+---------------+--------------+-----------------+-----------------+
| hyhScheduler | SUMMERDAY 01Trigger | DEFAULT | -1 | 5000 | 812 |
+--------------+---------------------+---------------+--------------+-----------------+-----------------+
1 row in set (0.00 sec)
mysql> select * from qrtz_cron_triggers;
+--------------+---------------------+---------------+------------------+---------------+
| SCHED_NAME | TRIGGER_NAME | TRIGGER_GROUP | CRON_EXPRESSION | TIME_ZONE_ID |
+--------------+---------------------+---------------+------------------+---------------+
| hyhScheduler | SUMMERDAY 02Trigger | DEFAULT | 0/10 * * * * ? * | Asia/Shanghai |
+--------------+---------------------+---------------+------------------+---------------+
原始碼下載
本文內容均為對優秀部落格及官方文件總結而得,原文地址均已在文中參考閱讀處標註。最後,文中的程式碼樣例已經全部上傳至Gitee:https://gitee.com/tqbx/springboot-samples-learn,另有其他SpringBoot的整合哦。