这里写自定义目录标题
1、认识定时任务框架
场景:1、活动的开始
2、定时更新数据
3、新闻的推送等等
概念:在指定的时间,去调用、执行特定的任务。
2、通过简单案例使用Quartz定时任务框架
2.1 新建maven项目
2.2 添加jar依赖
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Quartz任务调度-->
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
</dependencies>
3、定时任务的体系结构
1、新建任务 Job:要执行的业务代码
2、声明触发器 Trigger: 定义时间规则
3、声明调度器Scheduler: 把任务和触发器 关联在一起,开启即可
4、新建一个Job
package com.qf.quartz2205.jobs;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class FirstJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//编写指定的时间,具体要执行的任务
//获取当前任务的详细信息
JobDetail jobDetail=context.getJobDetail();
//开启活动
//更新缓存
//推送新闻等等
for(int i=1; i<=5; i++){
//jobDetail.getKey().getName() 获取的是当前任务的名字
//jobDetail.getKey().getGroup() 获取的是任务所在组的名字
System.out.println("任务名:"+jobDetail.getKey().getName()+
",任务所在组"+jobDetail.getKey().getGroup()+
"执行了i="+i);
}
}
}
5、在main方法中,定义时间规则及触发器
package com.qf.quartz2205.test;
import com.qf.quartz2205.jobs.FirstJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.GregorianCalendar;
public class Test1 {
public static void main(String[] args) {
//测试使用定时任务
//1、定义触发器:定义时间规则
Trigger trigger= TriggerBuilder.newTrigger()
.withIdentity("firstTrigger","triggerGroup1") // 参数1:触发器的名字,参数2:触发器所在的组名
.startNow() //触发器立刻开始生效,startAt() 指定Date类型的时间,在指定的时间生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3) //每隔3秒执行一次
.repeatForever()) //一直重复 也可以通过repeatCount(20) 指定重复执行的次数
.endAt(new GregorianCalendar(2023,4,17,11,20,0).getTime())
//endAt 结束的时间
.build();
try {
//2、定义调度器
Scheduler scheduler= StdSchedulerFactory.getDefaultScheduler();
//3、把任务封装在JobDetail类中
JobDetail jobDetail=JobBuilder.newJob(FirstJob.class)
.withIdentity("job1","jobGroup1")// 参数1:任务的名字 参数2:任务所在组的名
.build();
//通过调度器scheduler把trigger 和 jobdetail封装、关联在一起
scheduler.scheduleJob(jobDetail,trigger);
//开启
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
小结:
1、编写任务 实现Job接口
2、编写时间规则: Trigger
3、编写调度器: Scheduler
4、【说明】每一个任务都是一个独立的线程,每个定时任务,有自己的时间比较器
6、定时任务的线程池配置-了解
场景:当一个服务中的定时任务比较多的时候,为了高效使用定时任务,需要开启线程池。
【说明】线程池的好处:
1、节省自行创建、销毁线程的时间
2、线程池防止我们无限制的创建线程资源,导致程序崩溃
6.1 在项目的resources路径下,创建quartz.properties文件
6.2 在quartz.properties编写线程池的配置信息
# 线程池里的线程由谁来创建
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
# 线程池的类别
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 线程池中线程的数量
org.quartz.threadPool.threadCount = 10
# 设置线程池中线程的优先级
org.quartz.threadPool.threadPriority = 5
# 线程执行的任务,无需持久化
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
线程池的使用无需编写任何代码,直接使用即可
【面试题1】1、为什么使用springboot 而不使用spring spring mvc?
1、重约束、轻配置
2、内置了大量的组件,简化开发 例如tomcat
3、springboot的starter机制,节省了jar的依赖
【面试题2】如何理解springboot的重约束、轻配置?
1、springboot的启动类,放在父包中,无需配置扫描的基本包
2、配置文件的名字 application 自动读取
【面试题】springboot 如何使用外部的tomcat?
【面试题】springboot的starter是什么?如何自己开发一个starter
7、Trigger-重点
Trigger的作用:定义时间规则,在什么时候去执行任务。
分两种类型:
Trigger类型1:SimpleTrigger-了解
只能定义简单的时间规则:间隔多长时间,以及重复次数等
Trigger类型2:CronTrigger-重点
能够定义复杂的时间规则,通过Cron表达式来定义时间规则
一、组成规则:由6或7位组成
秒 分 时 日期 月份 星期 [年份]
【说明】日期、星期中,只要设置其中之一即可
二、符号组成
符号含义
, 表示列表,用于列表值的分隔 2,4,7,8,9 * * * * *
- 表示一个连续的范围 2,4,7-9 * * * * *
* 表示每一个,看具体出现的位置
/ 表示步长 递增多少 3/5 * * * * * 从第3秒开始,每过5秒执行定时任务
? 表示任意值,只能出现在日期 星期位置中,当设置了日期 星期中的任意一个,那么另一个就可以使用?
# 只能用在星期中, 星期几#第几个 案例 4#2 第个星期3 1-星期天 2-星期一 3-星期二 4-星期三
L 出现在日期或星期中,通常表示最后一个 10 20 5 ? 4 L 4月份每个星期的星期六的5时20分10秒的时候
10 20 5 ? 4 3L 4月的最后一个星期2的5时20分10秒
出现在星期的位置:如果单独出现,表示每个星期的最后一天,也就是星期六
如果XL ,表示当月的最后一个星期X-1
出现在日期的位置:表示当月的最后一天(自动计算大、小月份、平润年)
W 只能出现在日期中 XW 表示离X最近的一个工作日 22W 指的是21号 21W
注意不跨月
LW 出现在日期中,表示当月的最后一个工作日
符号 | 语义 |
---|---|
星号(*) | 可用在所有字段中,表示对应时间域的每一个时刻,例如, 在分钟字段时,表示“每分钟” |
问号(?) | 该字符只在日期和星期字段中使用,它通常指定为“不确定值” |
减号(-) | 表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12 |
逗号(,) | 表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五 |
斜杠(/) | x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段 中表示5,20,35,50 |
井号(#) | 该字符只用在星期字段中,"4#2"代表第二个星期3,“5#4”代表第4个星期四 |
L | 该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。 |
如果L用在星期字段里,则表示星期六,等同于7 | |
L出现在星期字段里,而且在前面有一个数值x,则表示“这个月的最后一个周x”,例如,6L表示该月的最后星期五 | |
L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号 | |
W | 该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日 |
例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号 是星期二,那结果就是15号星期二;但必须注意关联的匹配日期不能够跨月 | |
LW组合 | 在日期字段可以组合使用LW,它的意思是当月的最后一个工作日 |
练习:
表示式 | 说明 |
---|---|
0 0 12 * * ? | 每天的12:00:00 |
0 15 10 * * ? | 每天的10:15:00 |
0 15 10 * * ? 2008 | 2008年的每天的10:15:00 |
0 * 14 * * ? | 每天14时每一分的0秒开始执行一次 |
0 0/5 14 * * ? | 每天14时从0分开始每隔5分中的第0秒开始执行 |
0 0/5 14,18 * * ? | 每天打的14时、18时的从0分开始每隔5分中的0秒开始执行 |
0 0-5 14 * * ? | 每天的14时的0、1、2、3、4、5分的0秒开始执行 |
0 0-5/2 14 * * ? | 每天14时0分、2分、4分的0秒开始执行 |
0 10,44 14 ? 3 4 | 3月份的每周三的14时的10分、44分 0秒时执行 |
0 15 10 ? * 2-6 | 每个工作日的10:15:00执行 |
0 15 10 15 * ? | 每个月的15号的10:15:00 |
0 15 10 L * ? | 每月最后一天的10:15:00 |
0 15 10 ? * 6L | 每月的最后一个星期五的10:15:00 |
0 15 10 ? * 6L 2007-2009 | 2007、2008、2009三年中每月的最后一个星期五的10:15:00 |
8、使用CronTriggeer
package com.qf.quartz2205.test;
import com.qf.quartz2205.jobs.FirstJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class Test2 {
public static void main(String[] args) {
//使用CronTrigger
//1、定义CronTrigger
Trigger trigger= TriggerBuilder.newTrigger()
.withIdentity("cronTrigger","cronGroup1")
.withSchedule(CronScheduleBuilder.cronSchedule("0,5,13 * * 17 * ?"))
.build();
try {
//定义调度器
Scheduler scheduler= StdSchedulerFactory.getDefaultScheduler();
//把任务封装在JobDetail中
JobDetail jobDetail=JobBuilder.newJob(FirstJob.class)
.withIdentity("job2","jobGroup2")
.build();
//关联触发器和任务
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
9、任务的控制
package com.qf.quartz2205.service.impl;
import com.qf.quartz2205.jobs.FirstJob;
import com.qf.quartz2205.service.LoopService;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.stereotype.Service;
@Service
public class LoopServiceImpl implements LoopService {
//声明调度属性
private Scheduler scheduler;
@Override
public String startJob(String name, String group) {
String result="";
try {
//创建调度对象
scheduler= StdSchedulerFactory.getDefaultScheduler();
//创建CronTrigger
Trigger cronTrigger= TriggerBuilder.newTrigger()
.withIdentity("cronTrigger","griggerGroup")
.withSchedule(CronScheduleBuilder.cronSchedule("5,10,15 * * 18 4 ?"))
.build();
JobDetail jobDetail=JobBuilder.newJob(FirstJob.class)
.withIdentity(name,group)
.build();
scheduler.scheduleJob(jobDetail,cronTrigger);
scheduler.start();
result="start success";
} catch (SchedulerException e) {
result="start failed";
e.printStackTrace();
}
return result;
}
@Override
public String deleteJob(String name, String group) {
String result="";
//删除任务之前一定要开启任务
if(this.scheduler!=null){
//把任务的名字,组名封装在JobKey对象中
JobKey jobKey=JobKey.jobKey(name,group);
try {
scheduler.deleteJob(jobKey);
result="delete success";
} catch (SchedulerException e) {
result="delete failed";
e.printStackTrace();
}
}
return result;
}
}
9.1 删除任务
@Override
public String deleteJob(String name, String group) {
String result="";
//删除任务之前一定要开启任务
if(this.scheduler!=null){
//把任务的名字,组名封装在JobKey对象中
JobKey jobKey=JobKey.jobKey(name,group);
try {
scheduler.deleteJob(jobKey);
result="delete success";
} catch (SchedulerException e) {
result="delete failed";
e.printStackTrace();
}
}
return result;
}
3.2 暂停、恢复任务
@Override
public String pauseJob(String name, String group) {
String result="";
//把需要暂停的任务封装在JobKey中
JobKey jobKey=JobKey.jobKey(name,group);
//执行暂停
try {
scheduler.pauseJob(jobKey);
result="pause success";
} catch (SchedulerException e) {
result="pause failed";
e.printStackTrace();
}
return result;
}
@Override
public String resumeJob(String name, String group) {
String result="";
JobKey jobKey=JobKey.jobKey(name, group);
try {
scheduler.resumeJob(jobKey);
result="resume success";
} catch (SchedulerException e) {
result="resume failed";
e.printStackTrace();
}
return result;
}
3.3 任务的批量处理
可以一次性处理多个任务,暂停、恢复多个
【准备】又开启了一个任务,当前任务和之前的任务必须使用同一个Scheduler,否则批量操作失效
@Override
public String startJob2(String name, String group) {
String result="";
if(this.scheduler==null){
try {
scheduler=StdSchedulerFactory.getDefaultScheduler();
} catch (SchedulerException e) {
e.printStackTrace();
}
}else{
//定义时间规则
Trigger trigger=TriggerBuilder.newTrigger()
.withIdentity("trigger2","griggerGroup2")
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))
.build();
JobDetail jobDetail=JobBuilder.newJob(SecondJob.class)
.withIdentity(name,group)
.build();
try {
scheduler.scheduleJob(jobDetail,trigger);
result="start job2 success";
} catch (SchedulerException e) {
result="start job2 failed";
e.printStackTrace();
}
}
return result;
}
批量暂停、恢复
@Override
public String pauseJobs(String group) {
//把组名直接封装在GroupMatcher中
GroupMatcher<JobKey> groupMatcher=GroupMatcher.groupEquals(group);
String result="";
try {
scheduler.pauseJobs(groupMatcher);
result="批量暂停成功";
} catch (SchedulerException e) {
result="批量暂停失败";
e.printStackTrace();
}
return null;
}
@Override
public String resumeJobs(String group) {
GroupMatcher<JobKey> groupMatcher=GroupMatcher.groupEquals(group);
String result="";
try {
scheduler.resumeJobs(groupMatcher);
result="批量恢复成功";
} catch (SchedulerException e) {
result="批量恢复失败";
e.printStackTrace();
}
return result;
}