Quartz学习笔记
一、什么是Quartz
什么是Quartz?
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:
持久性作业 - 就是保持调度定时的状态;
作业管理 - 对调度作业进行有效的管理;
ps: 大部分公司都会用到定时任务这个功能。
拿火车票购票来说,当你下单后,后台就会插入一条待支付的task(job),一般是30分钟,超过30min后就会执行这个job,去判断你是否支付,未支付就会取消此次订单;当你支付完成之后,后台拿到支付回调后就会再插入一条待消费的task(job),Job触发日期为火车票上的出发日期,超过这个时间就会执行这个job,判断是否使用等。
在我们实际的项目中,当Job过多的时候,肯定不能人工去操作,这时候就需要一个任务调度框架,帮我们自动去执行这些程序。那么该如何实现这个功能呢?
(1)首先我们需要定义实现一个定时功能的接口,我们可以称之为Task(或Job),如定时发送邮件的task(Job),重启机器的task(Job),优惠券到期发送短信提醒的task(Job),实现接口如下:
(2)有了任务之后,还需要一个能够实现触发任务去执行的触发器,触发器Trigger最基本的功能是指定Job的执行时间,执行间隔,运行次数等。
(3)有了Job和Trigger后,怎么样将两者结合起来呢?即怎样指定Trigger去执行指定的Job呢?这时需要一个Schedule,来负责这个功能的实现。
上面三个部分就是Quartz的基本组成部分:
- 调度器:Scheduler
- 任务:JobDetail
- 触发器:Trigger,包括SimpleTrigger和CronTrigger
那么明白 Quartz了 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。
- Job 表示一个工作,要执行的具体内容。类似于TimerTask类。需要实现方法
void execute(JobExecutionContext context)
- JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
- Trigger 代表一个调度参数的配置,什么时候去调。
- Scheduler 代表一个调度容器。类似于Timer类。一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调用。
- JobListener: 任务执行的监听器,可以监听到任务执行前、后以及未能成功执行抛出异常。
二、快速开始
SpringBoot集成Quartz依然沿用了Spring的典型方式,使用工厂Bean生成Bean的方式。在Quartz中,需要被调度的任务叫做Job,而负责调度任务则是Scheduler。
我们首先需要配置工厂Bean:JobFactory接口,自定义一个AutowiringSpringBeanJobFactory类继承SpringBeanJobFactory(实现了JobFactory接口)AutowiringSpringBeanJobFactory工厂类将负责生成实现了Job接口的类的实例对象Bean。
1. 第一种集成 Springboot项目启动就执行
<!-- 导入quartz依赖 --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency>
快速入门,2步搞定
1.启动类上面加 @EnableScheduling
2.方法上面加上@Scheduled(cron = "0/3 * * * * ? ") 每3秒执行一次
cron资料: https://www.cnblogs.com/javahr/p/8318728.html
然后就可以启动看结果了!!!
2. 第二种动态开启定时任务
1.引入依赖(Springboot项目)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</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> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> <!-- mybatis启动器 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <!-- 集成jdbc --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> <exclusions> <exclusion> <artifactId>slf4j-api</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency>
2.配置文件可选配置
#是否开启监听器 -> 可以不写,默认为不开启
jobListener:
switch: true
3.创建一个执行任务的job类
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author daifeng
* @create 2021-11-01 15:35
* 定时任务具体实现类
*/
public class helloJob implements Job{
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 取出JobDataMap中的数据
System.out.println(jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail1"));
System.out.println(jobExecutionContext.getTrigger().getJobDataMap().get("trigger1"));
System.out.println("定时任务执行啦:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
3.创建JobListener监听器
package com.demo.quartz.config;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Component
public class SchedulerListener implements JobListener {
public static final String LISTENER_NAME = "QuartSchedulerListener";
@Override
public String getName() {
return LISTENER_NAME; //must return a name
}
//任务被调度前
@Override
public void jobToBeExecuted(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().toString();
System.out.println("任务被调度前");
System.out.println("Job : " + jobName + " is going to start...");
}
//任务调度被拒了
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
System.out.println("任务调度被拒了");
//可以做一些日志记录原因
}
//任务被调度后
@Override
public void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException) {
System.out.println("任务被调度后");
String jobName = context.getJobDetail().getKey().toString();
System.out.println("Job : " + jobName + " is finished...");
if (jobException!=null&&!jobException.getMessage().equals("")) {
System.out.println("Exception thrown by: " + jobName
+ " Exception: " + jobException.getMessage());
}
}
}
4.创建SchedulerConfig配置类
package com.demo.quartz.config;
import lombok.SneakyThrows;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.spi.JobFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import java.io.IOException;
import java.util.Properties;
/**
* 定时配置(可以配置静态定时任务)
*/
@Configuration
public class SchedulerConfig {
@Autowired
private SchedulerListener scheduleListener;
// 是否开启监听器 默认为false
@Value("${jobListener.switch:false}")
private boolean jobListener;
@Bean
public SchedulerFactory schedulerFactoryBean(){
return new StdSchedulerFactory();
}
@SneakyThrows
@Bean
public Scheduler scheduler(@Qualifier("schedulerFactoryBean") SchedulerFactory schedulerFactoryBean){
Scheduler scheduler = schedulerFactoryBean.getScheduler();
if(jobListener){
scheduler.getListenerManager().addJobListener(scheduleListener);
}
return scheduler;
}
}
5.自定义接口请求 执行定时任务
import com.demo.quartz.config.helloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* @author daifeng
* @create 2021-11-01 15:44
*/
@RestController
public class JobController {
// 调度器
@Autowired
private Scheduler scheduler;
// @Scheduled(cron = "0/3 * * * * ? ")
public void text(){
//每3秒执行一次
System.out.println("定时任务执行啦:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
/**
* 以请求的方式去开启定时任务
* 该任务的执行周期为 : 请求执行的时间起 每秒执行一次 且一直执行
* @throws SchedulerException
*/
@PostMapping(value = "testTask1")
public void testTask1( )throws SchedulerException {
//可用于传值给job 具体看helloJob类如何取值使用
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("jobDetail1","jobDetail1");
JobDataMap jobDataMap2 = new JobDataMap();
jobDataMap2.put("trigger1","这是jobDetail1的trigger");
// 1、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(helloJob.class).usingJobData(jobDataMap)
.withIdentity("job1", "group1").build();
// 2、构建Trigger(触发器)实例,每隔1s执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.usingJobData(jobDataMap2) //存值到jobDataMap Job类中能取出
// 可设置触发时间
.startNow()//立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)//每隔1s执行一次
.repeatForever()).build();//一直执行
//3、执行
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
/**
* 以请求的方式去开启定时任务
* 该任务的执行周期为 : 请求执行的时间起 每天早上8点都会执行一次 持续一周 即endAt的时间
* @throws SchedulerException
*/
@PostMapping(value = "testTask2")
public void testTask2( )throws SchedulerException {
// 获取一周后的日期
Date endDate = new Date();
Calendar now = Calendar.getInstance();
now.add(Calendar.DATE, +7);
Date endAt = now.getTime();
//可用于传值给job
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("jobDetail1","jobDetail1");
JobDataMap jobDataMap2 = new JobDataMap();
jobDataMap2.put("trigger1","这是jobDetail1的trigger");
// 1、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(helloJob.class).usingJobData(jobDataMap)
.withIdentity("job1", "group1").build();
// 2、构建Trigger实例,每隔1s执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.usingJobData(jobDataMap2) //存值到jobDataMap
.startNow() // 立即触发
.endAt(endAt) // 表示该任务在一周后自动停止 ps:表示触发器结束触发的时间;
//使用cron表达式 每天中午8点触发
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 8 * * ?"))
.build();
//3、执行
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
/**
* 以请求的方式去开启定时任务
* 该任务的执行周期为 : 请求执行的时间的第二天开始 每天早上8点都会执行一次 持续一周即endAt的时间
* @throws SchedulerException
*/
@PostMapping(value = "testTask3")
public void testTask3( )throws SchedulerException {
Calendar now = Calendar.getInstance();
now.setTime(new Date());
// 获取一周后的日期
now.add(Calendar.DATE, +7);
Date endAt = now.getTime();
// 获取一天后的日期
now.add(Calendar.DATE, +1);
Date startAt = now.getTime();
//可用于传值给job
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("jobDetail1","jobDetail1");
JobDataMap jobDataMap2 = new JobDataMap();
jobDataMap2.put("trigger1","这是jobDetail1的trigger");
// 1、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(helloJob.class).usingJobData(jobDataMap)
.withIdentity("job1", "group1").build();
// 2、构建Trigger实例,每隔1s执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.usingJobData(jobDataMap2) //存值到jobDataMap
.startAt(startAt) // ps:表示触发器开始触发的时间;
.endAt(endAt) // 表示该任务在一周后自动停止 ps:表示触发器结束触发的时间;
//使用cron表达式 每天中午8点触发
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 8 * * ?"))
.build();
//3、执行
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
/**
* 移除定时任务
* @param jobName JobDetail的name
* @param jobGroup JobDetail的分组名
* @throws SchedulerException
*/
@RequestMapping("/deleteJob")
public void deleteJob(String jobName,String jobGroup) throws SchedulerException
{
JobKey jobKey=new JobKey(jobName,jobGroup);
scheduler.deleteJob(jobKey);
}
/**
* 暂停定时任务
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
@RequestMapping("/pauseJob")
public void pauseJob(String jobName,String jobGroup) throws SchedulerException
{
JobKey jobKey=new JobKey(jobName,jobGroup);
scheduler.pauseJob(jobKey);
}
/**
* 恢复定时任务
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
@RequestMapping("/resumeJob")
public void resumeJob(String jobName,String jobGroup) throws SchedulerException
{
JobKey triggerKey=new JobKey(jobName,jobGroup);
scheduler.resumeJob(triggerKey);
}
/**
* 清空所有当前scheduler对象下的定时任务【目前只有全局一个scheduler对象】
* @throws SchedulerException
*/
@RequestMapping("/clearAll")
public void clearAll() throws SchedulerException {
scheduler.clear();
}
}
6.自定义工具类
package com.demo.quartz.utils;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;
@Component
public class SchedulerUtils {
// 调度器
@Autowired
private Scheduler scheduler;
/**
* 开始定时任务
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void startJob(String cron,String jobName,String jobGroup,Class<? extends Job> jobClass) throws SchedulerException
{
JobKey jobKey=new JobKey(jobName,jobGroup);
//判断是否存在相同的计划任务
if(!scheduler.checkExists(jobKey))
{
scheduleJob(cron,scheduler,jobName,jobGroup,jobClass);
}
}
/**
* 移除定时任务
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void deleteJob(String jobName,String jobGroup) throws SchedulerException
{
JobKey jobKey=new JobKey(jobName,jobGroup);
scheduler.deleteJob(jobKey);
}
/**
* 暂停定时任务
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void pauseJob(String jobName,String jobGroup) throws SchedulerException
{
JobKey jobKey=new JobKey(jobName,jobGroup);
scheduler.pauseJob(jobKey);
}
/**
* 恢复定时任务
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void resumeJob(String jobName,String jobGroup) throws SchedulerException
{
JobKey triggerKey=new JobKey(jobName,jobGroup);
scheduler.resumeJob(triggerKey);
}
/**
* 清空所有当前scheduler对象下的定时任务【目前只有全局一个scheduler对象】
* @throws SchedulerException
*/
public void clearAll() throws SchedulerException {
scheduler.clear();
}
/**
* 可根据需要灵活配置 这里只列举一种,具体如何灵活配置,可看(JobController中的3个testTask接口)
* 动态创建Job
* 此处的任务可以配置可以放到properties或者是放到数据库中
* Trigger:name和group 目前和job的name、group一致,之后可以扩展归类
* @param scheduler
* @throws SchedulerException
*/
private void scheduleJob(String cron,Scheduler scheduler,String jobName,String jobGroup,Class<? extends Job> jobClass) throws SchedulerException{
/*
* 此处可以先通过任务名查询数据库,如果数据库中存在该任务,更新任务的配置以及触发器
* 如果此时数据库中没有查询到该任务,则按照下面的步骤新建一个任务,并配置初始化的参数,并将配置存到数据库中
*/
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build();
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail,cronTrigger);
}
}
总结:
-
新建一个类实现Job并是实现void execute(JobExecutionContext var1) throws JobExecutionException;
-
创建JobDetail,封装Job,它是Scheduler(调度器)真正调度的对象,可以设置Job名称,组等信息,且名称不能重复
-
定义触发器Trigger,代表一个调度参数的配置,什么时候去调JobDetail,
-
创建就Scheduler,代表一个调度器 类似于Timer类。一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调用。
-
最后将jobDetail和 Trigger添加到Scheduler中,并启动
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
代码已上到gitee: https://gitee.com/Lazy_001/quartz.git