一、简介
Quartz是一款功能强大的开源任务调度框架,几乎可以集成到任何Java应用程序中(小到单机应用,大到分布式应用)。Quartz可用于创建简单或复杂的任务调度,用以执行数以万计的任务,并且支持任务持久化。任务被定义为标准化的Java组件,Java编写的任务都可以被执行。
二、核心概念
- Scheduler(调度器):Quartz中的任务调度器,通过Trigger和JobDetail可以用来调度、暂停和删除任务。
- Trigger(触发器):Quartz中的触发器,可以通过CRON表达式来指定任务执行的时间,时间到了会自动触发任务执行。
- JobDetail(任务详情):Quartz中需要执行的任务详情,包括了任务的唯一标识和具体要执行的任务,可以通过JobDataMap往任务中传递数据。
- Job(任务):Quartz中具体的任务,包含了执行任务的具体方法。
- Job Stores(持久化):
RAMJobStore:存内存RAM、快、缺点是当您的应用程序结束(或崩溃)时,所有调度信息都将丢失。
JDBC JobStore:存数据库、比RAM慢一点,加索引与机器性能OK那速度还好。
TerracottaJobStore:数据存储在Terracotta服务器中,速度介于RAM与DB之间,可以运行群集或非群集。
三、CRON表达式
1 Cron表达式是一个字符串,包括6~7个时间元素,在Quartz中可以用于指定任务的执行时间。
2 CRON的语法格式:Seconds Minutes Hours DayofMonth Month DayofWeek
3 在线CRON表达式生成器:https://cron.qqe2.com/
在线时间戳转换:https://tool.lu/timestamp/
四、自定义quartz框架API对接前端
1 初始化表:
SQL详见官网或者https://gitee.com/qiugantao/java-spring-quick-start/blob/master/document/sql/quartz.sql
表信息解析:
1.1.qrtz_blob_triggers : 以Blob 类型存储的触发器。
1.2.qrtz_calendars:存放日历信息, quartz可配置一个日历来指定一个时间范围。
1.3.qrtz_cron_triggers:存放cron类型的触发器。
1.4.qrtz_fired_triggers:存放已触发的触发器。
1.5.qrtz_job_details:存放一个jobDetail信息。
1.6.qrtz_job_listeners:job监听器。
1.7.qrtz_locks: 存储程序的悲观锁的信息(假如使用了悲观锁)。
1.8.qrtz_paused_trigger_graps:存放暂停掉的触发器。
1.9.qrtz_scheduler_state:调度器状态。
1.10.qrtz_simple_triggers:简单触发器的信息。
1.11.qrtz_trigger_listeners:触发器监听器。
1.12.qrtz_triggers:触发器的基本信息。
2 pom.xml添加依赖:
<!--SpringBoot集成QuartZ-推荐-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
3 yml配置:
server:
port: 8087
spring:
datasource:
url: jdbc:mysql://localhost:3307/qgt?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: 123456
quartz:
jdbc:
initialize-schema: never # 设置是否初始化表(该配置目前版本没有生效,设置成always也不会自动建表,根据官网文档查看,其目的是自动将quartz需要的数据表通过配置方式进行初始化)
job-store-type: jdbc # quartz任务存储类型:jdbc或memory
wait-for-jobs-to-complete-on-shutdown: true # 关闭是等待任务完成
overwrite-existing-jobs: true # 可以覆盖已有的任务
properties: # quartz原生配置
org:
quartz:
scheduler:
instanceName: scheduler # 调度器实例名称
instanceId: AUTO # 调度器实例ID自动生成
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX # 调度信息存储处理类
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 使用完全兼容JDBC的驱动
tablePrefix: QRTZ_ # quartz相关表前缀
useProperties: true # 是否将JobDataMap中的属性转为字符串存储
threadPool:
class: org.quartz.simpl.SimpleThreadPool # 指定线程池实现类,对调度器提供固定大小的线程池
threadCount: 10 # 设置并发线程数量
threadPriority: 5 # 指定线程优先级
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:mapper/*Mapper.xml
4 编写job:
@Component("helloJob")
public class HelloJob implements BaseJob {
private static Logger log = LoggerFactory.getLogger(HelloJob.class);
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail jobDetail = context.getJobDetail();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
log.info("Hello Job执行时间: " + DateFormatUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
}
public HelloJob() {}
}
5 编写service接口:
public interface ScheduleService {
/**
* 添加任务
*/
Boolean scheduleJob(JobInfo jobInfo);
/**
* 更新任务
*/
Boolean reScheduleJob(String jobName, String jobGroup, String cronExpression) throws Exception;
/**
* 暂停任务
*/
Boolean pausejob(String jobName, String jobGroup) throws Exception;
/**
* 恢复任务
*/
Boolean resumejob(String jobName, String jobGroup) throws Exception;
/**
* 取消任务
*/
Boolean cancelScheduleJob(String jobName);
/**
* 查询任务
*/
List<JobAndTrigger> getJobAndTriggerDetails(int pageNum, int pageSize);
}
6 编写serviceImpl定时任务实现类:
@Slf4j
@Service
public class ScheduleServiceImpl implements ScheduleService {
@Autowired
private Scheduler scheduler;
@Autowired
private JobAndTriggerMapper jobAndTriggerMapper;
@Override
public Boolean scheduleJob(JobInfo jobInfo) {
boolean success = false;
// 创建需要执行的任务
JobDetail jobDetail = null;
try {
jobDetail = JobBuilder.newJob(getClass(jobInfo.getJobClassName()).getClass())
.withIdentity(jobInfo.getJobClassName(), jobInfo.getJobGroupName())
// .usingJobData("data", data)
.build();
} catch (Exception e) {
e.printStackTrace();
}
//创建触发器,指定任务执行时间
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity(jobInfo.getJobClassName(), jobInfo.getJobGroupName())
.withSchedule(CronScheduleBuilder.cronSchedule(jobInfo.getCronExpression()))
.build();
//使用调度器进行任务调度
try {
scheduler.scheduleJob(jobDetail, cronTrigger);
success = true;
} catch (SchedulerException e) {
e.printStackTrace();
log.info("创建定时任务失败!");
} catch (Exception e) {
e.printStackTrace();
log.info("创建定时任务失败1!");
}
return success;
}
/**
* 根据类名称,通过反射得到该类,然后创建一个BaseJob的实例。
* 由于NewJob和HelloJob都实现了BaseJob,
* 所以这里不需要我们手动去判断。这里涉及到了一些java多态调用的机制
* 2019 12/24更新不再使用反射,使用SpringUtil工具类;
* 前提是具体的Job类必须交给Spring来管理;
* 例如:HelloJob 在其类上加上@Component("helloJob")注解,并指定传入Bean的名称;
* @param classname
* @return
* @throws Exception
*/
public BaseJob getClass(String classname) throws Exception {
//Class<?> class1 = Class.forName(classname);
//BaseJob baseJob = (BaseJob) class1.newInstance();
BaseJob baseJob = (BaseJob) SpringUtil.getBean(classname);
return baseJob;
}
@Override
public Boolean reScheduleJob(String jobName, String jobGroup, String cronExpression) throws Exception{
boolean success = false;
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
success = true;
} catch (SchedulerException e) {
log.info("更新定时任务失败" + e);
throw new Exception("更新定时任务失败");
} catch (Exception e) {
log.info("更新定时任务失败1" + e);
throw new Exception("更新定时任务失败1");
}
return success;
}
@Override
public Boolean pausejob(String jobName, String jobGroup) throws Exception {
boolean success = false;
try {
scheduler.pauseJob(JobKey.jobKey(jobName, jobGroup));
success = true;
} catch (SchedulerException e) {
log.info("暂停定时任务失败" + e);
throw new Exception("暂停定时任务失败");
} catch (Exception e) {
log.info("暂停定时任务失败1" + e);
throw new Exception("暂停定时任务失败1");
}
return success;
}
@Override
public Boolean resumejob(String jobName, String jobGroup) throws Exception {
boolean success = false;
try {
scheduler.resumeJob(JobKey.jobKey(jobName, jobGroup));
success = true;
} catch (SchedulerException e) {
log.info("恢复定时任务失败" + e);
throw new Exception("恢复定时任务失败");
} catch (Exception e) {
log.info("恢复定时任务失败1" + e);
throw new Exception("恢复定时任务失败1");
}
return success;
}
@Override
public Boolean cancelScheduleJob(String jobName) {
boolean success = false;
try {
// 暂停触发器
scheduler.pauseTrigger(new TriggerKey(jobName, defaultGroup));
// 移除触发器中的任务
scheduler.unscheduleJob(new TriggerKey(jobName, defaultGroup));
// 删除任务
scheduler.deleteJob(new JobKey(jobName, defaultGroup));
success = true;
} catch (SchedulerException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return success;
}
@Override
public List<JobAndTrigger> getJobAndTriggerDetails(int pageNow, int pageSize){
int pageNum = (pageNow-1)*pageSize;
return jobAndTriggerMapper.getJobAndTriggerDetails(pageNum,pageSize);
}
}
7 编写定时任务调度相关接口:
@Api(tags = "ScheduleController", description = "定时任务调度相关接口")
@RestController
@RequestMapping("/schedule")
public class ScheduleController {
@Autowired
private ScheduleService scheduleService;
@ApiOperation("新增定时任务")
@PostMapping("/scheduleJob")
public CommonResult scheduleJob(JobInfo jobInfo) throws Exception{
Boolean success = scheduleService.scheduleJob(jobInfo);
return CommonResult.success(success);
}
/**
*
* @param jobName 查询条件,查不到会返回500
* @param jobGroup 查询条件,查不到会返回500
* @param cronExpression 需要更新的Cron表达式
* @return
* @throws Exception
*/
@ApiOperation("更新定时任务")
@PostMapping(value = "/reschedulejob")
public CommonResult rescheduleJob(@RequestParam(value = "jobName") String jobName,
@RequestParam(value = "jobGroup") String jobGroup,
@RequestParam(value = "cronExpression") String cronExpression) throws Exception {
Boolean success = scheduleService.reScheduleJob(jobName, jobGroup, cronExpression);
return CommonResult.success(success);
}
@ApiOperation("暂停定时任务")
@PostMapping(value = "/pausejob")
public CommonResult pausejob(@RequestParam(value = "jobName") String jobName, @RequestParam(value = "jobGroup") String jobGroup) throws Exception {
Boolean success = scheduleService.pausejob(jobName, jobGroup);
return CommonResult.success(success);
}
@ApiOperation("恢复定时任务")
@PostMapping(value = "/resumejob")
public CommonResult resumejob(@RequestParam(value = "jobName") String jobName, @RequestParam(value = "jobGroup") String jobGroup) throws Exception {
Boolean success = scheduleService.resumejob(jobName, jobGroup);
return CommonResult.success(success);
}
@ApiOperation("取消定时任务")
@PostMapping("/cancelScheduleJob")
public CommonResult cancelScheduleJob(@RequestParam String jobName) {
Boolean success = scheduleService.cancelScheduleJob(jobName);
return CommonResult.success(success);
}
@ApiOperation("查询定时任务")
@GetMapping(value = "/queryScheduleJob")
public CommonResult queryScheduleJob(@ApiParam(name = "pageNow", value = "页码", required = true, defaultValue = "1")
@RequestParam int pageNow,
@ApiParam(name = "pageSize", value = "每页数量", required = true, defaultValue = "3")
@RequestParam int pageSize) {
List<JobAndTrigger> jobs = scheduleService.getJobAndTriggerDetails(pageNow,pageSize);
return CommonResult.success(jobs);
}
}
8 效果:
demo源码:https://gitee.com/qiugantao/java-spring-quick-start/tree/master/java-quartz1