一、什么是Quartz?
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:
- 持久性作业 - 就是保持调度定时的状态;
- 作业管理 - 对调度作业进行有效的管理;
二、java定时任务调度的实现方式
(1)Timer
特点是:简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务;能实现简单的定时任务,稍微复杂点(或要求高一些)的定时任务却不好实现。
(2)ScheduledExecutor
鉴于Timer的缺陷,Java 5推出了基于线程池设计的ScheduledExecutor;
特点:每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。
虽然用ScheduledExecutor和Calendar能够实现复杂任务调度,但实现起来还是比较麻烦,对开发还是不够友善。
(3)Spring Scheduler
spring对任务调度的实现支持,可以指定任务的执行时间,但对任务队列和线程池的管控较弱;一般集成于项目中,小任务很方便。
(4)开源工具包 JCronTab
JCronTab则是一款完全按照crontab语法编写的java任务调度工具。
特点:
- 可指定任务的执行时间;
- 提供完全按照Unix的UNIX-POSIX crontab的格式来规定时间;
- 支持多种任务调度的持久化方法,包括普通文件、数据库以及 XML 文件进行持久化;
- JCronTab内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人;
- 设计和部署是高性能并可扩展。
(5)开源工具包 Quartz
- 具有强大的调度功能,很容易与spring集成,形成灵活可配置的调度功能;
- 调度环境的持久化机制:可以保存并恢复调度现场,即使系统因为故障关闭,任务调度现场的数据并不会丢失;timer没有这些特点;
- 灵活的应用方式:可以灵活的定义触发器调度的时间表,并可以对触发器与任务进行关联映射;
- 分布式与集群能力;
三、quartz的相关概念
Scheduler:调度器,进行任务调度;quartz的大脑
Job:业务job,亦可称业务组件;定时任务的具体执行业务需要实现此接口,调度器会调用此接口的execute方法完成我们的定时业务
JobDetail:用来定义业务Job的实例,我们可以称之为quartz job,很多时候我们谈到的job指的是JobDetail
Trigger:触发器,用来定义一个指定的Job何时被执行
JobBuilder:Job构建器,用来定义或创建JobDetail的实例;JobDetail限定了只能是Job的实例
TriggerBuilder:触发器构建器,用来定义或创建触发器的实例
四、如何在springboot中集成Quartz
(1)在pom.xml中添加依赖
<!--quartz依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
(2)appliaction.yml中添加配置(不可用,不知为何……问了同事,只说quartz官方推荐quartz.properties的配置方式)
spring:
quartz:
# 将任务等保存化到数据库
job-store-type: jdbc
# 程序结束时会等待quartz相关的内容结束
wait-for-jobs-to-complete-on-shutdown: true
# QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录
overwrite-existing-jobs: true
# 这里居然是个map,搞得智能提示都没有,佛了
properties:
org:
quartz:
# scheduler相关
scheduler:
# scheduler的实例名
instanceName: scheduler
instanceId: AUTO
# 持久化相关
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 表示数据库中相关表是QRTZ_开头的
tablePrefix: QRTZ_
useProperties: false
# 线程池相关
threadPool:
class: org.quartz.simpl.SimpleThreadPool
# 线程数
threadCount: 10
# 线程优先级
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
(2')添加配置quartz.properties
#quartz集群配置
# ===========================================================================
# Configure Main Scheduler Properties 调度器属性
# ===========================================================================
#调度标识名 集群中每一个实例都必须使用相同的名称
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#ID设置为自动获取 每一个必须不同
org.quartz.scheduler.instanceid=AUTO
#============================================================================
# Configure ThreadPool
#============================================================================
#线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#指定线程数,至少为1(无默认值)(一般设置为1-100直接的整数合适)
org.quartz.threadPool.threadCount = 25
#设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority = 5
#============================================================================
# Configure JobStore
#============================================================================
# 信息保存时间 默认值60秒
org.quartz.jobStore.misfireThreshold = 60000
#数据保存方式为数据库持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
#JobDataMaps是否都为String类型
org.quartz.jobStore.useProperties = true
#数据库别名 随便取
org.quartz.jobStore.dataSource = myDS
#表的前缀,默认QRTZ_
org.quartz.jobStore.tablePrefix = QRTZ_
#是否加入集群
org.quartz.jobStore.isClustered = true
#调度实例失效的检查时间间隔
org.quartz.jobStore.clusterCheckinInterval = 50000
#============================================================================
# Configure Datasources
#============================================================================
#数据库引擎
#org.quartz.dataSource.myDS.driver = org.postgresql.Driver
##数据库连接
#org.quartz.dataSource.myDS.URL = jdbc:postgresql://20.20.1.205:5432/quartz
##数据库用户
#org.quartz.dataSource.myDS.user = postgres
##数据库密码
#org.quartz.dataSource.myDS.password = passwd
##允许最大连接
#org.quartz.dataSource.myDS.maxConnections = 5
##验证查询sql,可以不设置
#org.quartz.dataSource.myDS.validationQuery=select 0 from dual
(3) 直接上代码
这次我们写的高级些,将调度任务持久化到数据库,对前端暴露接口,通过接口来操作定时任务,更加灵活且便于管理。
https://blog.csdn.net/qq_37339399/article/details/108141744
(i)新建Quartz相关的数据库表,SQL在哪里找?
依赖包中找到Maven:org.quartz-scheduler:quartz:2.3.0 ==> org ==> quartz ==> impl ==> jdbcjobstore 文件夹下对应各个方言的sql文件,找到合适的就可以了
各个表的含义:
qrtz_fired_triggers; ------ 存储以触发的Trigger相关的状态信息,以及关联的JOb信息
qrtz_paused_trigger_grps; ------ 存储已暂停的Trigger组的信息
qrtz_scheduler_state; ------ 存储少量的有关Scheduler的状态信息
qrtz_locks; ------ 存储程序的悲观锁信息
qrtz_simple_triggers; ------ 存储simpleTrigger,包括重复次数,间隔,以及已执行次数
qrtz_simprop_triggers; ------ 存储CalendarIntervalTrigger等其他类的trigger
qrtz_cron_triggers; ------ 存储cronTrigger的基本信息,包括表达式,时区
qrtz_blob_triggers; ------ 将Trigger作为Blob类型存储,用于用户自定义Trigger类型
qrtz_triggers; ------ 存储已配置trigger的信息
qrtz_job_details; ------ 存储每一个已配置的Job的详细信息
qrtz_calendars; ------ 以Blob形式存储Quartz的calendar信息
注:Blob数据类型:是一个变长的二进制字符串(二进制大对象)
(ii)项目启动时加载
/**
* Quartz的相关配置,注册JobDetail和Trigger
* 注意JobDetail和Trigger是org.quartz包下的,不是spring包下的,不要导入错误
*/
@Component
public class QuartzConfig {
/**
* 将job的实例化交给IOC去进行
*/
public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
protected Object creatJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
//进行注入
beanFactory.autowireBean(job);
return job;
}
}
//从quartz.properties文件中读取Quartz配置属性
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
/**
* 配置JobFactory
* @param applicationContext
* @return
*/
@Bean
public JobFactory jobFactory(ApplicationContext applicationContext) {
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
/**
* 自定义配置任务工厂
* @param jobFactory
* @param dataSource
* @return
* @throws Exception
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setJobFactory(jobFactory);
//设置覆盖已存在的任务
schedulerFactoryBean.setOverwriteExistingJobs(true);
//schedulerFactoryBean.setStartupDelay(2);
//设置数据源,使用与项目统一数据源
schedulerFactoryBean.setDataSource(dataSource);
//设置上下文spring bean name
schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
//设置项目启动时开始任务
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.setQuartzProperties(quartzProperties());
return schedulerFactoryBean;
}
}
(iii)controller层
@RestController
public class QuartzController {
@Autowired
private QuartzService quartzService;
/**
* 新增定时任务
* @param jobDTO
*/
@RequestMapping("/qz/addJob")
public void addJob(@RequestBody JobDTO jobDTO) {
quartzService.addJob(jobDTO);
}
}
入参实体:
@Data
public class JobDTO {
private String jobId;
private String jobName;
private String groupName;
private String cron;
private String description;
private String status;
private String parameter;
private String type;
private Class T;
private String jobClass;
}
(iv)service层
public interface QuartzService {
void addJob(@RequestBody JobDTO jobDTO);
}
(v)serviceImpl层
@Slf4j
@Service
public class QuartzServiceImpl implements QuartzService {
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
@Override
public void addJob(JobDTO jobDTO) {
try {
// 获取调度器
Scheduler scheduler = schedulerFactoryBean.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(DynamicJob.class).withIdentity(jobDTO.getJobName(), jobDTO.getGroupName())
// .setJobData(castMap(jobDTO))
.build();
// 基于表达式构建触发器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobDTO.getCron());
// TriggerBuilder 用于构建触发器实例
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobDTO.getJobName(), jobDTO.getGroupName())
.withSchedule(cronScheduleBuilder).build();
//调度任务
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
(vi)创建个DynamicJob类实现Job接口,来写需要的定时业务逻辑
@Slf4j
@DisallowConcurrentExecution
public class DynamicJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 根据不同的type 执行不同任务
//JobDetail中的JobDataMap是共用的,从getMergedJobDataMap获取的JobDataMap是全新的对象
JobDataMap map = jobExecutionContext.getMergedJobDataMap();
String type = SpringUtil.objectToString(map.get("type"));
switch (type){
case "one":
this.doOne(map);
break;
case "two":
this.doTwo(map);
break;
case "three":
this.doThree(map);
break;
default:
log.info("nothing todo");
break;
}
}
private void doOne(JobDataMap map){
log.info("do one,description:{},jobId:{}",map.get("jobDescription"),map.get("jobId"));
}
private void doTwo(JobDataMap map){
log.info("do two,description:{},jobId:{}",map.get("jobDescription"),map.get("jobId"));
}
private void doThree(JobDataMap map){
log.info("do three,description:{},jobId:{}",map.get("jobDescription"),map.get("jobId"));
}
}
(vii)使用postman调用添加job接口
localhost:8070/qz/addJob
入参格式:
{
"jobId":"26",
"jobName":"job名称26",
"cron": "*/5 * * * * ?",
"type":"one"
}
可以在控制台看到每五秒钟打印一条定时日志,且入参的job信息已自动添加到数据库quartz相关表中。