什么是Quartz?
quartz是一个由java编写的任务调度库,由OpenSymphony组织开源出来。绝大多数公司都会用到任务调度这个功能, 比如公司需要定期执行任务调度生成报表, 或者比如博客什么的定时更新之类的,都可以靠Quartz来完成。
任务调度:现在有N个任务(程序),要求在指定时间执行,比如每周二3点执行任务A、每天相隔5s执行任务B等等,这种多任务拥有多种执行策略就是任务调度。而quartz的核心作用,是使任务调度变得丰富、高效、安全,开发者只需要调几个quartz接口并做简单配置,即可实现上述需求。
quartz号称能够同时对上万个任务进行调度,拥有丰富的功能特性,包括任务调度、任务持久化、可集群化、插件等
Quartz有哪些体系结构?(介绍和Cron表达式)
quartz体系结构:
1、Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;
2、JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
3、Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等等
4、Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日历特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一个日历时间点)。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。针对不同时间段类型,Quartz在org.quartz.impl.calendar包下提供了若干个Calendar的实现类,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周进行定义;
5、Scheduler: 代表一个Quartz的独立运行容器, Trigger和JobDetail可以注册到Scheduler中, 两者在Scheduler中拥有各自的组及名称, 组及名称是Scheduler查找定位容器中某一对象的依据, Trigger的组及名称必须唯一, JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法, 允许外部通过组及名称访问和控制容器中Trigger和JobDetail。
Scheduler可以将Trigger绑定到某一JobDetail中, 这样当Trigger触发时, 对应的Job就被执行。一个Job可以对应多个Trigger, 但一个Trigger只能对应一个Job。可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。 可以通过Scheduler.getContext()获取对应的SchedulerContext实例;
6、ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率
Cron表达式特殊字符意义示意表:
特殊字符 | 意义 |
---|---|
* | 匹配所有的值。如:*在分钟的字段域里表示 每分钟 |
? | 只在日期域和星期域中使用。它被用来指定“非明确的值” |
- | 指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点” |
, | 指定几个可选值。如:“MON,WED,FRI”在星期域里表示“星期一、星期三、星期五” |
/ | 指定增量。如:“0/15”在秒域意思是没分钟的0,15,30和45秒。“5/15”在分钟域表示没小时的5,20,35和50。符号“*”在“/”前面(如:*/10)等价于0在“/”前面(如:0/10) |
L | 表示day-of-month和day-of-week域,但在两个字段中的意思不同,例如day-of-month域中表示一个月的最后一天。如果在day-of-week域表示‘7’或者‘SAT’,如果在day-of-week域中前面加上数字,它表示一个月的最后几天,例如‘6L’就表示一个月的最后一个星期五 |
W | 只允许日期域出现。这个字符用于指定日期的最近工作日。例如:如果你在日期域中写 “15W”,表示:这个月15号最近的工作日。所以,如果15号是周六,则任务会在14号触发。如果15好是周日,则任务会在周一也就是16号触发。如果是在日期域填写“1W”即使1号是周六,那么任务也只会在下周一,也就是3号触发,“W”字符指定的最近工作日是不能够跨月份的。字符“W”只能配合一个单独的数值使用,不能够是一个数字段,如:1-15W是错误的 |
LW | L和W可以在日期域中联合使用,LW表示这个月最后一周的工作日 |
# | 只允许在星期域中出现。这个字符用于指定本月的某某天。例如:“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周)。“2#1”表示本月第一周的星期一。“4#5”表示第五周的星期三 |
C | 允许在日期域和星期域出现。这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。如:日期域是“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。星期域是“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一) |
Cron 表达式特殊字符意义对应表:
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒 | 0-59 | , - * / |
分 | 0-59 | , - * / |
小时 | 0-23 | , - * / |
月内日期 | 1-31 | , - * ? / L W C |
月 | 1-12 或者 JAN-DEC | , - * / |
周内日期 | 1-7 或者 SUN-SAT | , - * ? / L C # |
年(可选) | 留空, 1970-2099 | , - * / |
springboot 怎么使用Quartz?
首先你得准备一个springboot框架(用idea随便造一个,hahaha...)
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
在主程序类加上注解:@EnableScheduling
创建我们需要的任务记录表t_schedule_trigger
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `t_schedule_trigger`;
CREATE TABLE `t_schedule_trigger` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_name` varchar(100) NOT NULL,
`job_group` varchar(50) NOT NULL,
`job_class` varchar(200) NOT NULL,
`job_desc` varchar(200) NOT NULL,
`cron` varchar(100) NOT NULL,
`trigger_name` varchar(100) NOT NULL,
`trigger_group` varchar(50) NOT NULL,
`trigger_desc` varchar(200) NOT NULL,
`status` char(1) NOT NULL,
`account` int(11) NOT NULL,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `job_name` (`job_name`),
UNIQUE KEY `trigger_name` (`trigger_name`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `t_schedule_trigger` VALUES ('1', '愚人节的问候', '愚人组', 'com.manubao.quartz.job.HelloJob', '愚人节发个ILOVEYOU', '*/5 * * * * ?', '愚人节触发器', 'trigger1', '触发器描述', '1', '1', '2020-12-08 09:26:41');
INSERT INTO `t_schedule_trigger` VALUES ('2', '清明节的问候', '清明组', 'com.manubao.quartz.job.QmJob', '清明节发个IMISSYOU', '*/10 * * * * ?', '清明节触发器', 'trigger1', '清明节描述', '1', '1', '2020-12-08 09:26:41');
INSERT INTO `t_schedule_trigger` VALUES ('3', 'test', 'test', 'com.manubao.quartz.job.MyJob', '测试成功啦', '*/3 * * * * ?', 'test触发', 'trigger1', 'test', '1', '1', '2020-12-08 14:15:15');
创建对应的实体类(自己见一下哈-_-)
Dao层接口:
//添加任务
void addTrigger(Trigger trigger);
//获得所有任务
List<Trigger> getTriggers();
对应的XML:
<insert id="addTrigger" useGeneratedKeys="true" keyProperty="id">
insert into t_schedule_trigger( JOB_NAME, JOB_GROUP, JOB_CLASS, JOB_DESC, CRON, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_DESC, STATUS, ACCOUNT, CREATE_TIME)
VALUES(#{jobName},#{jobGroup},#{jobClass},#{jobDesc},#{cron},#{triggerName},#{triggerGroup},#{triggerDesc},#{status},#{account},#{createTime})
</insert>
<select id="getTriggers" resultType="com.manubao.quartz.model.Trigger">
select * from t_schedule_trigger where status=1
</select>
Service层:
//添加任务
void addTrigger(Trigger trigger);
//获得所有任务
List<Trigger> getTriggers();
//定时更新数据库表中的任务(重要)
public void refreshTrigger();
Service层实现类:
@Service
public class TriggerServiceImpl implements TriggerService {
//自动装配QuartzConfigration配置类中的Scheduler
@Autowired
private Scheduler scheduler;
//装配Dao层接口
@Autowired
private TriggerMapper triggerMapper;
@Override
public void addTrigger(Trigger trigger) {
this.triggerMapper.addTrigger(trigger);
}
@Override
public List<Trigger> getTriggers() {
return this.triggerMapper.getTriggers();
}
//定时更新任务
@Override
@Scheduled(cron = "*/5 * * * * ?")
public void refreshTrigger() {
try {
//查询出数据库中所有的定时任务
List<Trigger> jobList = triggerMapper.getTriggers();
if (jobList != null) {
for (Trigger trigger : jobList) {
//获得每个任务触发器的状态
int status = trigger.getStatus();
//JobBuild中的JobKey是表明Job身份的一个对象,里面封装了Job的name和group
//TriggerBuild中的TriggerKey封装了Trigger的name和group
TriggerKey triggerKey = TriggerKey.triggerKey(trigger.getJobName(), trigger.getJobGroup());
CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
//说明本条任务还没有添加到quartz中
if (null == cronTrigger) {
if (status==0) { //如果是禁用,则不用创建触发器
continue;
}
JobDetail jobDetail = null;
try {
//创建JobDetail(数据库中job_name存的任务全路径,这里就可以动态的把任务注入到JobDetail中)
jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(trigger.getJobClass())) //trigger.getJobName() => "com.manubao.quartz.MyJob1"
.withIdentity(trigger.getJobName(), trigger.getJobGroup())
.build();
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(trigger.getCron());
//按新的cronExpression表达式构建一个新的trigger
cronTrigger = TriggerBuilder.newTrigger()
.withIdentity(trigger.getJobName(), trigger.getJobGroup())
.withSchedule(scheduleBuilder)
.build();
//把trigger和jobDetail注入到调度器
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} else {
//说明查出来的这条任务,已经设置到quartz中了
// Trigger已存在,先判断是否需要删除,如果不需要,再判定是否时间有变化
if (status==0) { //如果是禁用,从quartz中删除这条任务
JobKey jobKey = JobKey.jobKey(trigger.getJobName(), trigger.getJobGroup());
scheduler.deleteJob(jobKey);
continue;
}
String searchCron = trigger.getCron(); //获取数据库的
String currentCron = cronTrigger.getCronExpression();
if (!searchCron.equals(currentCron)) { //说明该任务有变化,需要更新quartz中的对应的记录
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(searchCron);
//按新的cronExpression表达式重新构建trigger
cronTrigger = cronTrigger.getTriggerBuilder()
.withIdentity(triggerKey)
.withSchedule(scheduleBuilder)
.build();
//按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, cronTrigger);
}
}
}
}
} catch (Exception e) {
System.out.println("定时任务每日刷新触发器任务异常,在ScheduleTriggerServiceImpl的方法refreshTrigger中,异常信息:"+e);
throw new RuntimeException(e);
}
}
}
新建Job类:(里面主要就是你想要的操作)
@Component
@Slf4j
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("测试HelloJob....");
System.out.println(new Date());
}
}
@Component
@Slf4j
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("测试MyJob....");
System.out.println(new Date());
}
}
@Component
@Slf4j
public class QmJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("测试QQmJob....");
System.out.println(new Date());
}
}
启动类:加注解
@SpringBootApplication
@MapperScan("com.manubao.quartz.dao")
@EnableTransactionManagement
@EnableScheduling
案例测试?
启动项目之后查看控制台:
看到这些,那么,恭喜你