SpringBoot - 集成Quartz实现定时任务(内存篇)
SpringBoot定时任务 — Quartz最快速的集成及使用(定时任务未持久化)
一、前言
1.定时任务框架选型
1)简单的有Java自带的Timer、
ScheduledExecutorService,
Spring自带的Task。
2)相较复杂的分布式定时任务中间件有XXL-JOB、ElasticJob等。
2.为什么是Quartz
简单的适用场景过于单一无法处理复杂的业务,而复杂的相较Quartz过于复杂且学习成本较高,仅用于新手学习而言Quartz较为合适。
3.文中涉及到的源码地址
二、引入
SpringBoot从2.0起整合了Quartz,因此只需要引入一个starter即可食用。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.0.3.RELEASE</version> <!-- 当前发布的最新版本 -->
</dependency>
三、配置
在application.yml中做如下配置即可。
spring:
quartz:
job-store-type: memory #所有任务相关内容存储在内存中
scheduler-name: AnchorScheduler
properties:
org.quartz.scheduler.instanceId: 1122334 #集群中会用到,单节点无用,不填、填AUTO都可以
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 9 #线程数
org.quartz.threadPool.threadPriority: 5 #线程优先级
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000 #作业最大延迟时间,毫秒
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore #内存作业时负责跟踪调度所有工作数据
注:
SpringBoot会根据yml中spring.quartz下的内容自动注入相关的Bean,具体实现代码在QuartzAutoConfiguration.class中。
四、基本
1)Quartz中有三个基本”组件”,由它们共同来定义、运行一个定时任务:
— JobDetail,定时任务中的“任务”;
— Trigger,定时任务中的“定时”;
— Scheduler,定时任务的调度器(组装器);
2)还有一些常用的类:
— JobKey,用于唯一标识一个JobDetail;
— TriggerKey,用于唯一标识一个Trigger;
五、使用
1.创建任务
1)在Quartz中创建的所有定时任务都要实现Job接口,但是在SpringBoot中所有的定时任务只要继承QuartzJobBean类即可。
2)QuartzJobBean是一个抽象类,实现了Quartz的Job接口。
2)与Thread的run()方法类似,定时任务的具体实现写在executeInternal()方法中。
3)每创建一个新的定时任务,都需要新建一个Java类并继承QuartzJobBean、实现executeInternal()。
//SimpleJob是一个简单定时任务体,用于打印出当前线程名、当前时间及当前调用次数
public class SimpleJob extends QuartzJobBean {
private final static AtomicInteger counter = new AtomicInteger(1);
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
LocalDateTime now = LocalDateTime.now();
String name = Thread.currentThread().getName();
System.out.println("Execute quartz \"SimpleJob\", threadName = \"" + name +
"\", the " + counter.getAndIncrement() + "th execution, time = \"" +
now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) + "\"");
}
}
2.创建JobDetail
使用建造者模式的JobBuilder来创建一个JobDetail对象。
JobDetail simpleJob = JobBuilder.newJob(SimpleJob.class) //传入一个Job类
.withIdentity("SimpleJob", "AnchorJobs") //(name, group)标识唯一一个JobDetail
.storeDurably() //在没有Trigger关联的情况下保存该任务到调度器
.build();
注:
1)newJob()中传入的Job类必须是继承了QuartzJobBean的类。
2)withIdentity()中group可不传,不传时默认设为”DEFAULT”。
3)storeDurably()使JobDetail可在没有关联Trigger的情况下添加到调度器中,否则会抛异常。建议调用此方法。
3.创建Trigger
常用Trigger有两种:SimpleTrigger和CronTrigger。二者最大的区别是CronTrigger支持Cron表达式,更灵活,因此使用CronTrigger居多。
1)创建SimpleTrigger
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(5) //每5秒执行一次
.repeatForever(); //无限循环执行
SimpleTrigger simpleTrigger = TriggerBuilder.newTrigger()
.withIdentity("SimpleTrigger", "AnchorTriggers") //(name, group)唯一标识一个Trigger
.startNow()
.withSchedule(scheduleBuilder)
.build();
2)创建CronTrigger
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?"); //Cron表达式,每5秒执行一次
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity("CronJob", "AnchorTriggers") //(name, group)唯一标识一个Trigger
.startNow() //调用scheduler.scheduleJob()后立即开始执行定时任务
.withSchedule(scheduleBuilder) //不同的scheduleBuilder
.build();
4.启动任务
@Resource
private Scheduler scheduler; //SpringBoot会根据我们上文的配置自动装载Scheduler,直接注入即可使用
public void start() {
//或scheduler.scheduleJob(simpleJob, cronTrigger);
scheduler.scheduleJob(simpleJob, simpleTrigger);
}
注:
1)一个Job只能绑定一个Trigger。同一个Job不能用两个Trigger去触发,同一个Trigger也不能去触发两个Job。
2)创建并启动一个定时任务的正常流程是:创建任务类 ——> 创建JobDetail ——> 创建Trigger ——> scheduler启动任务
六、增删查改
增删查改都是基于Scheduler提供方法的二次封装。
1.新增
由于SimpleTrigger的局限性,这里只封装了新增使用CronTrigger的任务的方法。
/**
* className是任务类的类名,cronExpression是Cron表达式
*/
public Date addAndStartCronJob(String className, String cronExpression) throws Exception {
Class clazz = null;
try {
//用于校验类是否存在并实例化该的任务类
String entireClassName = JOB_PACKAGE + "." + className;
clazz = Class.forName(entireClassName);
} catch (ClassNotFoundException e) {
throw new Exception("Class \"" + className + "\" doesn't exist.");
}
//将类名作为Quartz的任务名,“类名 + Trigger”作为Trigger名
JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(className, JOB_GROUP).storeDurably().build();
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(className + "Trigger", TRIGGER_GROUP)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
return scheduler.scheduleJob(jobDetail, trigger); //新增并启动
}
注:
JOB_PACKAGE、JOB_GROUP、TRIGGER_GROUP是三个String常量,JOB_PACKAGE是任务类所在包的包名。
2.删除
这里删除分为两类:暂停和删除。
1)暂停
public void pauseJob(String className) throws Exception {
JobKey jobKey = new JobKey(className, JOB_GROUP);
if (scheduler.checkExists(jobKey)) {
scheduler.pauseJob(jobKey); //存在对应的恢复方法scheduler.resumeJob(jobKey);
} else {
throw new Exception("Job \"" + className + "\" doesn't exist.");
}
}
public void pauseAll() throws SchedulerException {
scheduler.pauseAll(); //存在对应的恢复方法scheduler.resumeAll(jobKey);
}
2)删除
public boolean deleteJob(String className) throws Exception {
JobKey jobKey = new JobKey(className, JOB_GROUP);
if (scheduler.checkExists(jobKey)) {
return scheduler.deleteJob(jobKey); //
} else {
throw new Exception("Job \"" + className + "\" doesn't exist.");
}
}
注:
暂停和删除scheduler都有批量操作的接口,这里未做封装。需要说明的是暂停的批量操作是针对group,删除的批量操作是针对(name,group)。
3.查询
封装了两个查询接口:根据状态查询定时任务列表、查询某个定时任务的状态。
public List<String> getJobsByState(String queryState) throws Exception {
if (!TRIGGER_STATUS.containsKey(queryState)) {
throw new Exception("queryState doesn't exist.");
}
List<String> jobNames = new ArrayList<>();
List<String> jobGroupNames = scheduler.getJobGroupNames(); //获取所有注册到调度器任务的group
for (String groupName : jobGroupNames) {
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) { //获取每个group下的所有任务
String triggerName = jobKey.getName() + "Trigger";
Trigger.TriggerState state = scheduler.getTriggerState(new TriggerKey(triggerName, TRIGGER_GROUP)); //获取当前任务的执行状态
//比对状态
if (state.equals(TRIGGER_STATUS.get(queryState))) { //TRIGGER_STATUS是个Map,封装了TriggerState
jobNames.add(jobKey.getName());
}
}
}
return jobNames;
}
public String getJobStatus(String className) throws Exception {
JobKey jobKey = new JobKey(className, JOB_GROUP);
if (scheduler.checkExists(jobKey)) {
String triggerName = className + "Trigger";
TriggerKey triggerKey = new TriggerKey(triggerName, TRIGGER_GROUP);
Trigger.TriggerState state = scheduler.getTriggerState(triggerKey);
return JOB_STATUS.get(state); //JOB_STATUS是个Map,封装了TriggerState
} else {
throw new Exception("Job \"" + className + "\" doesn't exist.");
}
}
注:
Scheduler有个getCurrentlyExecutingJobs()方法,但是此方法获取的是请求瞬间正在执行的任务。
4.修改
用于修改某个任务的Cron。
public Date modifyJobCron(String className, String cronExpression) throws Exception {
try {
//用于校验改任务的实现类是否存在
String entireClassName = JOB_PACKAGE + "." + className;
Class clazz = Class.forName(entireClassName);
} catch (ClassNotFoundException e) {
throw new Exception("Class \"" + className + "\" doesn't exist.");
}
String triggerName = className + "Trigger";
TriggerKey triggerKey = new TriggerKey(triggerName, TRIGGER_GROUP);
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
return scheduler.rescheduleJob(triggerKey, trigger); //更新并立即执行任务
}