一、Quartz核心概念
接口 | 含义 |
---|---|
scheduler | 任务调度器 |
job | 任务,即被调度的任务 |
JobDetail | 用于定义Job实例 |
JobBuilder | 用于定义、创建JobDetail实例 |
Trigger | 调度器基于特定时间来执行指定任务的组件 |
TriggerBuilder | 用于定义、创建Trigger实例 |
二、Quartz体系结构
明白Quartz怎么用,首先要了解Scheduler(调度器)、Job(任务)和Trigger(触发器)这3个核心的概念。
- Scheduler
代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。
- Job
是一个接口,只定义一个方法execute(JobExecutionContext context),在实现接口的execute方法中编写所需要定时执行的Job(任务), JobExecutionContext类提供了调度应用的一些信息。Job运行时的信息保存在JobDataMap实例中;
- JobDetail
Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角
- Trigger
Trigger是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;
- Calendar
org.quartz.Calendar和java.util.Calendar不同,它是一些日历特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一个日历时间点,无特殊说明后面的Calendar即指org.quartz.Calendar)。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。
三、采用内存方式使用Quartz
- Maven依赖
<!--spring boot集成quartz-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
- 创建任务
DateTimeJob类
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateTimeJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//获取JobDetail中关联的数据
String msg = (String) jobExecutionContext.getJobDetail().getJobDataMap().get("msg");
System.out.println("current time :"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "---" + msg);
}
}
MyCronJob类
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.Date;
public class MyCronJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//获取JobDetail中关联的数据
System.out.println("任务执行了" + new Date());
}
}
- 配置定时任务
创建一个QuartzConfig类,并添加@Configuration注解
import com.csdn.integrationweb.job.DateTimeJob;
import com.csdn.integrationweb.job.MyCronJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzConfig {
// 使用jobDetail包装job
@Bean
public JobDetail printTimeJobDetail(){
return JobBuilder.newJob(DateTimeJob.class)//PrintTimeJob我们的业务类
.withIdentity("DateTimeJob")//可以给该JobDetail起一个id
//每个JobDetail内都有一个Map,包含了关联到这个Job的数据,在Job类中可以通过context获取
.usingJobData("msg", "Hello Quartz")//关联键值对
.storeDurably()//即使没有Trigger关联时,也不需要删除该JobDetail
.build();
}
@Bean
public Trigger printTimeJobTrigger() {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/1 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(printTimeJobDetail())//关联上述的JobDetail
.withIdentity("quartzTaskService")//给Trigger起个名字
.withSchedule(cronScheduleBuilder)// 以某种触发器触发
.build();
}
// 使用jobDetail包装job
@Bean
public JobDetail myCronJobDetail() {
return JobBuilder.newJob(MyCronJob.class)
.withIdentity("myCronJob")
.storeDurably()//即使没有Trigger关联时,也不需要删除该JobDetail
.build();
}
// 把jobDetail注册到Cron表达式的trigger上去
@Bean
public Trigger CronJobTrigger() {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(myCronJobDetail()) //关联上述的JobDetail
.withIdentity("myCronJobTrigger")//给Trigger起个名字
.withSchedule(cronScheduleBuilder)// 以某种触发器触发。
.build();
}
}
- 测试
- 结构
四、Quartz相关信息存数据库的方式
由于要将信息存放到数据库,所以要和数据库打交道了,可以直接在上面代码的基础上集成mybatis等和数据库打交道的功能。
- Maven依赖
<!--spring boot集成quartz-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
- 创建任务
DateTimeJob类
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateTimeJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//获取JobDetail中关联的数据
String msg = (String) jobExecutionContext.getJobDetail().getJobDataMap().get("msg");
System.out.println("current time :"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "---" + msg);
}
}
MyCronJob类
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.Date;
public class MyCronJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//获取JobDetail中关联的数据
System.out.println("任务执行了" + new Date());
}
}
- 配置定时任务
创建一个QuartzConfig类,并添加@Configuration注解
import com.csdn.integrationweb.job.DateTimeJob;
import com.csdn.integrationweb.job.MyCronJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzConfig {
// 使用jobDetail包装job
@Bean
public JobDetail printTimeJobDetail(){
return JobBuilder.newJob(DateTimeJob.class)//PrintTimeJob我们的业务类
.withIdentity("DateTimeJob")//可以给该JobDetail起一个id
//每个JobDetail内都有一个Map,包含了关联到这个Job的数据,在Job类中可以通过context获取
.usingJobData("msg", "Hello Quartz")//关联键值对
.storeDurably()//即使没有Trigger关联时,也不需要删除该JobDetail
.build();
}
@Bean
public Trigger printTimeJobTrigger() {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/1 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(printTimeJobDetail())//关联上述的JobDetail
.withIdentity("quartzTaskService")//给Trigger起个名字
.withSchedule(cronScheduleBuilder)// 以某种触发器触发
.build();
}
// 使用jobDetail包装job
@Bean
public JobDetail myCronJobDetail() {
return JobBuilder.newJob(MyCronJob.class)
.withIdentity("myCronJob")
.storeDurably()//即使没有Trigger关联时,也不需要删除该JobDetail
.build();
}
// 把jobDetail注册到Cron表达式的trigger上去
@Bean
public Trigger CronJobTrigger() {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(myCronJobDetail()) //关联上述的JobDetail
.withIdentity("myCronJobTrigger")//给Trigger起个名字
.withSchedule(cronScheduleBuilder)// 以某种触发器触发。
.build();
}
}
- 添加schema
如果不需要每次启动服务重新初始化创建Quartz相关的表则不需要放schema文件,只需要事先在数据库中创建好相关表即可,直接进入下一步。
在resources目录下创建schema文件夹,并将mysql的脚本文件放到该目录下,如果使用的不是mysql数据库,可以直接在Quartz官网下载(下载文件docs/dbTables文件内),下载地址:http://d2zwv9pap9ylyd.cloudfront.net/quartz-2.2.3-distribution.tar.gz
schema文件下下tables_mysql.sql内容如下
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;
CREATE TABLE QRTZ_JOB_DETAILS
(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_SIMPLE_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CRON_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(200) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
STR_PROP_1 VARCHAR(512) NULL,
STR_PROP_2 VARCHAR(512) NULL,
STR_PROP_3 VARCHAR(512) NULL,
INT_PROP_1 INT NULL,
INT_PROP_2 INT NULL,
LONG_PROP_1 BIGINT NULL,
LONG_PROP_2 BIGINT NULL,
DEC_PROP_1 NUMERIC(13,4) NULL,
DEC_PROP_2 NUMERIC(13,4) NULL,
BOOL_PROP_1 VARCHAR(1) NULL,
BOOL_PROP_2 VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_BLOB_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CALENDARS
(
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_FIRED_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
CREATE TABLE QRTZ_SCHEDULER_STATE
(
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
CREATE TABLE QRTZ_LOCKS
(
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
commit;
- 在application.yml里添加相应配置
server:
port: 80
spring:
datasource:
url: jdbc:mysql://localhost/sys_quartz?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
maxActive: 20
initialSize: 5
maxWait: 60000
minIdle: 5
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
connectionProperties:
druid.stat.mergeSql: true
druid.stat.slowSqlMillis: 5000
maxOpenPreparedStatements: 20
filters: stat,wall,log4j
logSlowSql: true
## quartz
# 采用数据库存储方式
quartz:
job-store-type: jdbc
# 每次启动重新初始化数据库中Quartz相关的表,如果不需要每次启动服务都重新创建表,下面两项可以不配置,事先在数据库中创建好Quartz相关的表
jdbc:
initialize-schema: always
# 初始化脚本
schema: classpath:schema/tables_mysql.sql
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#配置驼峰不自动转换
map-underscore-to-camel-case: false
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.klg.cod.entity
global-config:
logic-not-delete-value: 0
logic-delete-value: 1
五、cron表达式
注:下面内容主要参考Quartz的文档,对于Spring Task基本都适用。
cron表达式是由7个域组成的字符串,它们描述了任务计划的各个细节,这些域用空格分隔,每个域代表的含义如下:
- Seconds(秒)
- Minutes(分)
- Hours(时)
- Day-of-Month(日)
- Month(月)
- Day-of-Week(星期)
- Year(可选字段)(年)
示例:0 0 10 ? * WED
表示每个星期三的10:00:00
表达式 | {秒} | {分} | {时} | {日} | {月} | {周} | {年}(可选){年}(可选) |
---|---|---|---|---|---|---|---|
允许值 | 0~59 | 0~59 | 0~23 | 1~31 | 1~12 JAN~DEC | 1~7 SUN~SAT | 1970~2099 |
特殊值 | , - * / | , - * / | , - * / | , - * /? L W C | , - * / | , - * /? L C # | , - * / |
说明:下面描述中,XX域则表示cron表达式相应的位置,如秒域表示cron中第1个值,日域则表示cron表达式第4个值等等。
-
月份简称:JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV和DEC。
-
星期简称:SUN,MON,TUE,WED,THU,FRI和SAT。其中,1表示SUN。
-
,:用来分割在域上指定的多个值。如:MON,WED,FRI在星期域里表示星期一、星期三、星期五。
-
/:用于指定增量值。如分钟上使用0/15,表示从零开始,每隔15分钟,等价于0,15,30,45。如分钟上使用3/15,表示从第3分钟开始,每隔15分钟,等价于3,18,33,48,x/y中x表示开始值,y表示步长。
-
:表示匹配该域的任意值。如秒上使用表示每秒触发一次。
-
-:表示指定一个范围,如分钟域上10-13,表示10分、11分、12分、13分。
-
?:表示不关心的域,可用于日和周两个域上,主要用来解决日和周两个域的冲突。和类似,区别在于关心域,只是域的值可以任意,?则表示对该域不关心,不需要看该域的内容,直接忽略。
-
L:表示最后,是单词last的首字母,可用于日和周两个域上,用在日和周上含义不同:
- 日域上表示月份中日期的最后一天,如一月的第31天、非闰年二月的第28天。
- 周域上单独使用仅表示7或SAT,即仅表示周六。但是如果跟在其他值后,如6L或FRIL则表示该月中最后一个星期五。
- L还可以指定偏移量,如日域指定L-3,表示该月倒数第3天。当使用L时其值尽量不要指定列表或范围,以免令人困惑。
-
W:用于日域,表示距离指定日最近的星期几(周一至周五中的一个),如:日域上值为15W则表示距离本月第15日最近的工作日。
-
#:用于周域,表示该月的第n个星期几。如:周域值为6#3或FRI#3表示该月的第3个星期五。
六、常用表达式示例
- 0 0 10,14,16 * * ?每天上午10点、下午两点、下午4点整触发
- 0 0/30 9-17 * * ? 每天朝九晚五内每隔半小时触发
- 0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
- 0 0/5 * * * ?每5分钟触发
- 10 0/5 * * * ?每隔5分钟的第10秒触发(即10:00:10、10:05:10、10:10:10等)
- 30 * * * * ? 每半分钟触发
- 30 10 * * * ? 每小时的10分30秒触发
- 30 10 1 * * ? 每天1点10分30秒触发
- 30 10 1 20 * ? 每月20号1点10分30秒触发
- 30 10 1 20 10 ? * 每年10月20号1点10分30秒触发
- 30 10 1 20 10 ? 2011 2011年10月20号1点10分30秒触发
- 30 10 1 ? 10 * 2011 2011年10月每天1点10分30秒触发
- 30 10 1 ? 10 SUN 2011 2011年10月每周日1点10分30秒触发
- 15,30,45 * * * * ? 每15秒,30秒,45秒时触发
- 15-45 * * * * ? 15到45秒内,每秒都触发
- 15/5 * * * * ? 每分钟的每15秒开始触发,每隔5秒触发一次
- 15-30/5 * * * * ? 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次
- 0 0/3 * * * ? 每小时的第0分0秒开始,每三分钟触发一次
- 0 15 10 ? * MON-FRI 星期一到星期五的10点15分0秒触发
- 0 15 10 L * ? 每个月最后一天的10点15分0秒触发
- 0 15 10 LW * ? 每个月最后一个工作日的10点15分0秒触发
- 0 15 10 ? * 5L 每个月最后一个星期四的10点15分0秒触发
- 0 15 10 ? * 5#3 每个月第三周的星期四的10点15分0秒触发