Quartz是由Java语言编写的开源作业调度框架,它包含调度器监听、作业和触发器监听,允许通过时间间隔来调度作业。
1. 下载
这里下载主要是想拿它的数据库初始化脚本而已,后面使用它主要使用maven依赖的方式。
部分内容如下:
主要是初始化Quartz框架需要用到的表。
2. Spring Boot项目集成
对于原有项目,也可以如下直接集成:
这里使用的MySQL数据库:
3. 增加quartz配置文件(resources/quartz.properties)和配置类
#使用自己的配置文件
org.quartz.jobStore.useProperties:true
#默认或是自己改名字都行
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
#如果使用集群,instanceId必须唯一,设置成AUTO
org.quartz.scheduler.instanceId = AUTO
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
#存储方式使用JobStoreTX,也就是数据库
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#是否使用集群(如果项目只部署到 一台服务器,就不用了)
#org.quartz.jobStore.isClustered = true
#org.quartz.jobStore.clusterCheckinInterval=20000
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = myDS
#配置数据源
#数据库中quartz表的表名前缀
org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://xxxx:xx/my_test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=round&allowMultiQueries=true
org.quartz.dataSource.myDS.user = xxx
org.quartz.dataSource.myDS.password = xxxxxx
org.quartz.dataSource.myDS.maxConnections = 5
配置类如下:
package cn.linjk.quartz.config;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
@Configuration
public class QuartzConfig {
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) throws IOException {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setOverwriteExistingJobs(true);
schedulerFactoryBean.setQuartzProperties(quartzProperties());
return schedulerFactoryBean;
}
}
4. 作业调度测试
流程如下:
第一步,可以由controller接口调用,也可以由其他service类调用触发
第二步,通常作为service:
package cn.linjk.quartz.service;
import java.util.List;
import cn.linjk.quartz.dao.ICSchedulerTrigger;
import cn.linjk.quartz.domain.CScheduleTrigger;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ScheduleTriggerService {
private static final Logger logger = LoggerFactory.getLogger(ScheduleTriggerService.class);
@Autowired private Scheduler scheduler;
@Autowired private ICSchedulerTrigger triggerRepository;
// 简单触发器,通常用于间隔时间短执行指定任务,可以指定重复次数后取消任务的周期执行
public void simpleTrigger() {
try {
CScheduleTrigger scheduleTrigger = triggerRepository.getSimpleTrigger();
if (scheduleTrigger != null) {
Integer status = scheduleTrigger.getStatus();
TriggerKey triggerKey = TriggerKey.triggerKey(scheduleTrigger.getJobName(), scheduleTrigger.getJobGroup());
SimpleTrigger simpleTrigger = (SimpleTrigger) scheduler.getTrigger(triggerKey);
if (null == simpleTrigger) {
if (status == 0) {
// 任务状态为0,即禁用状态,不执行,退出
return;
}
String gap = scheduleTrigger.getCron();
if (gap.contains("S")) {
gap = gap.substring(0, gap.indexOf("S"));
}
JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(scheduleTrigger.getJobName()))
.withIdentity(scheduleTrigger.getJobName(), scheduleTrigger.getJobGroup()).build();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
jobDataMap.put("param_1", "param1");
// 一个scheduler 可以绑定多个job
// 一个 Job 在同一个 Scheduler 实例中通过名称和组名能唯一被标识
// 任务被配置为每5秒执行一次,共执行四次(注意参数为3)
SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(Integer.valueOf(gap));
builder.withRepeatCount(3);
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(scheduleTrigger.getJobName(), scheduleTrigger.getJobGroup()).startNow()
.withSchedule(builder).build();
// 执行4次后,需要可以重新循环执行时,需要如下语句,否则执行完,再次触发周期任务无效
trigger = trigger.getTriggerBuilder().withIdentity(scheduleTrigger.getJobName(), scheduleTrigger.getJobGroup()).build();
scheduler.scheduleJob(jobDetail, trigger);
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
// cron任务,指定时间周期执行,格式与linux的cron任务一样
public void refreshTrigger() {
try {
//查询出数据库中所有的定时任务
List<CScheduleTrigger> jobList = triggerRepository.findAll();
if (jobList != null) {
for (CScheduleTrigger scheduleJob : jobList) {
Integer status = scheduleJob.getStatus(); //该任务触发器目前的状态
TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
//说明本条任务还没有添加到quartz中
if (null == trigger) {
if (status == 0) { //如果是禁用,则不用创建触发器
continue;
}
JobDetail jobDetail = null;
try {
//创建JobDetail(数据库中job_name存的任务全路径,这里就可以动态的把任务注入到JobDetail中)
jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(scheduleJob.getJobName())).withIdentity(scheduleJob.getJobName(), scheduleJob.getJobGroup()).build();
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCron());
///设置定时任务的时间触发规则
trigger = TriggerBuilder.newTrigger().withIdentity(scheduleJob.getJobName(), scheduleJob.getJobGroup()).withSchedule(scheduleBuilder).build();
//把trigger和jobDetail注入到调度器
scheduler.scheduleJob(jobDetail, trigger);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} else { //说明查出来的这条任务,已经设置到quartz中了
// Trigger已存在,先判断是否需要删除,如果不需要,再判定是否时间有变化
if (status == 0) { //如果是禁用,从quartz中删除这条任务
JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
scheduler.deleteJob(jobKey);
continue;
}
String searchCron = scheduleJob.getCron(); //获取数据库的
String currentCron = trigger.getCronExpression();
if (!searchCron.equals(currentCron)) { //说明该任务有变化,需要更新quartz中的对应的记录
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(searchCron);
//按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
}
}
}
}
} catch (Exception e) {
logger.error("定时任务刷新触发器任务异常,异常信息:", e);
}
}
}
第三步,数据库增加配置表,配置定时任务信息:
记录1是cron表达式的任务
记录2是周期间隔任务,配置为6秒执行一次,即: 6 seonds per-time
job_name是java类的路径,在第二步的service会利用反射找到执行的类,执行对应的业务逻辑。
第四步,业务任务类实现,路径名对应数据库表配置:
package cn.linjk.quartz.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SimpleTrigger;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component
public class MySimpleTask implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
try {
//可以通过context拿到执行当前任务的quartz中的很多信息,如当前是哪个trigger在执行该任务
SimpleTrigger trigger = (SimpleTrigger) context.getTrigger();
String jobName = trigger.getKey().getName();
String jobGroup = trigger.getKey().getGroup();
System.out.println("jobName:"+jobName);
System.out.println("jobGroup:"+jobGroup);
}catch (Exception e){
e.printStackTrace();
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("间隔任务执行任务中 " + simpleDateFormat.format(new Date()));
}
}
在间隔任务中,我如下直接获取指定的任务:
5. 运行测试,可以增加一个controller调用,类似如下:
调用后,会看到调度器在执行,此时,即使重启该微服务,重启后,不需要重新触发任务执行,quartz框架会自动从数据库表中获取配置,重新执行周期任务,除非改了任务的禁用或启用状态。