一、Quartz引言
Quartz是一套轻量级的任务调度框架,只需要定义了 Job(任务),Trigger(触发器)和 Scheduler(调度器),即可实现一个定时调度能力。支持基于数据库的集群模式,可以做到任务幂等执行。
由于项目需要一个在web页面可以操作的任务调度管理,springboot的调度注解@Scheduled需要配置值,无法运行时更改。所以他们都无法在运行时通过代码或者其他自定义的方式实现动态调度。因此我选择使用Quartz来进行开发
二、依赖引入
org.springframework.boot
spring-boot-starter-quartz
三、创建定时任务的表结构
CREATE TABLE `scheduled_job` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键' ,
`bean_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'bean名称' ,
`method_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '方法名称' ,
`method_params` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '方法参数' ,
`cron_expression` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'cron表达式' ,
`job_status` int NULL DEFAULT NULL COMMENT '状态(1正常 0暂停)' ,
`remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注' ,
`create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间' ,
`update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间' ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
COMMENT='定时任务管理表'
ROW_FORMAT=DYNAMIC
;
四、实体类
@TableName("schedule_job")
@Data
public class ScheduleJob implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 任务id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* spring bean名称
*/
@TableField("name")
private String name;
/**
* 方法名
*/
@TableField("method_name")
private String methodName;
/**
* cron表达式
*/
@TableField("cron")
private String cron;
/**
* 任务状态 0:正常 1:暂停 -1:删除
*/
@TableField("status")
private Integer status;
/**
* 创建时间
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
/**
* 参数
*/
@TableField("params")
private String params;
}
五、接口定义
public interface ScheduleJobService extends IService {
/**
* 保存定时任务
*
*/
void add(ScheduleJobParam param);
/**
* 更新定时任务
*
*/
void delete(ScheduleJobParam param);
/**
* 更新
*
*/
void update(ScheduleJobParam param);
/**
* 查询单条数据,Specification模式
*
*/
ScheduleJobResult findBySpec(ScheduleJobParam param);
/**
* 查询列表,Specification模式
*
*/
List findListBySpec(ScheduleJobParam param);
/**
* 查询分页数据,Specification模式
*
*/
LayuiPageInfo findPageBySpec(ScheduleJobParam param);
/**
* 启用定时器
* @param jobIds
*/
void run(List jobIds);
/**
* 暂停定时器
* @param jobIds
*/
void pause(List jobIds);
/**
* 恢复定时器
* @param jobIds
*/
void resume(List jobIds);
/**
* 批量删除定时任务
*/
void deleteBatch(List jobIds);
/**
* 批量更新定时任务状态
*/
int updateBatch(List jobIds, int status);
}
六、接口实现
@Service
public class ScheduleJobServiceImpl extends ServiceImpl implements ScheduleJobService {
@Autowired
private Scheduler scheduler;
/**
* 项目启动时,初始化定时器
*/
@PostConstruct
public void init(){
List scheduleJobList = this.baseMapper.selectList(Wrappers.lambdaQuery().ne(ScheduleJob::getStatus,ConstCodes.DEL));
for(ScheduleJob scheduleJob : scheduleJobList){
CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getId());
//如果不存在,则创建
if(cronTrigger == null) {
ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
}else {
ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void add(ScheduleJobParam param){
ScheduleJob entity = getEntity(param);
this.save(entity);
ScheduleUtils.createScheduleJob(scheduler, entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(ScheduleJobParam param){
ScheduleUtils.deleteScheduleJob(scheduler,param.getId());
this.removeById(getKey(param));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(ScheduleJobParam param){
ScheduleJob oldEntity = getOldEntity(param);
ScheduleJob newEntity = getEntity(param);
ToolUtil.copyProperties(newEntity, oldEntity);
this.updateById(newEntity);
ScheduleUtils.updateScheduleJob(scheduler, newEntity);
}
@Override
public ScheduleJobResult findBySpec(ScheduleJobParam param){
return null;
}
@Override
public List findListBySpec(ScheduleJobParam param){
return null;
}
@Override
public LayuiPageInfo findPageBySpec(ScheduleJobParam param){
Page pageContext = getPageContext();
QueryWrapper objectQueryWrapper = new QueryWrapper<>();
IPage page = this.page(pageContext, objectQueryWrapper);
return LayuiPageFactory.createPageInfo(page);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void run(List jobIds) {
for(Long id : jobIds) {
ScheduleUtils.run(scheduler,this.baseMapper.selectById(id));
}
updateBatch(jobIds,ConstCodes.NORMAL);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void pause(List jobIds) {
for(Long id : jobIds) {
ScheduleUtils.pauseJob(scheduler, id);
}
//更新状态
updateBatch(jobIds,ConstCodes.PAUSE);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void resume(List jobIds) {
for(Long id : jobIds) {
ScheduleUtils.resumeJob(scheduler, id);
}
//更新状态
updateBatch(jobIds,ConstCodes.NORMAL);
}
/**
* 更新状态
*/
@Override
public int updateBatch(List jobIds, int status){
return this.baseMapper.updateBatch(jobIds,status);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteBatch(List jobIds) {
for(Long jobId : jobIds){
ScheduleUtils.deleteScheduleJob(scheduler, jobId);
}
//批量删除
this.baseMapper.deleteBatch(jobIds);
}
private Serializable getKey(ScheduleJobParam param){
return param.getId();
}
private Page getPageContext() {
return LayuiPageFactory.defaultPage();
}
private ScheduleJob getOldEntity(ScheduleJobParam param) {
return this.getById(getKey(param));
}
private ScheduleJob getEntity(ScheduleJobParam param) {
ScheduleJob entity = new ScheduleJob();
ToolUtil.copyProperties(param, entity);
return entity;
}
}
七、ScheduleUtils工具类
public class ScheduleUtils {
private final static String JOB_NAME = "TASK_";
/**
* 获取触发器key
*/
public static TriggerKey getTriggerKey(Long jobId) {
return TriggerKey.triggerKey(JOB_NAME + jobId);
}
/**
* 获取jobKey
*/
public static JobKey getJobKey(Long jobId) {
return JobKey.jobKey(JOB_NAME + jobId);
}
/**
* 获取表达式触发器
*/
public static CronTrigger getCronTrigger(Scheduler scheduler, Long jobId) {
try {
return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));
} catch (SchedulerException e) {
new ServiceException(BizExceptionEnum.SCHEDULE_TRIGGER_ERROR);
}
return null;
}
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, ScheduleJob scheduleJob) {
try {
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(ScheduleJobUtils.class).withIdentity(getJobKey(scheduleJob.getId())).build();
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCron())
.withMisfireHandlingInstructionDoNothing();
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJob.getId())).withSchedule(scheduleBuilder).build();
//放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(ConstCodes.JOB_PARAM_KEY, scheduleJob);
scheduler.scheduleJob(jobDetail, trigger);
//暂停任务
if(scheduleJob.getStatus() == ConstCodes.PAUSE){
pauseJob(scheduler, scheduleJob.getId());
}
} catch (SchedulerException e) {
throw new ServiceException(BizExceptionEnum.SCHEDULE_CREATE_ERROR);
}
}
/**
* 更新定时任务
*/
public static void updateScheduleJob(Scheduler scheduler, ScheduleJob scheduleJob) {
try {
TriggerKey triggerKey = getTriggerKey(scheduleJob.getId());
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCron())
.withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getId());
//按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//参数
trigger.getJobDataMap().put(ConstCodes.JOB_PARAM_KEY, scheduleJob);
scheduler.rescheduleJob(triggerKey, trigger);
//暂停任务
if(scheduleJob.getStatus() == ConstCodes.PAUSE){
pauseJob(scheduler, scheduleJob.getId());
}
} catch (SchedulerException e) {
throw new ServiceException(BizExceptionEnum.SCHEDULE_UPDATE_ERROR);
}
}
/**
* 立即执行任务
*/
public static void run(Scheduler scheduler, ScheduleJob scheduleJob) {
try {
//参数
JobDataMap dataMap = new JobDataMap();
dataMap.put(ConstCodes.JOB_PARAM_KEY, scheduleJob);
scheduler.triggerJob(getJobKey(scheduleJob.getId()), dataMap);
} catch (SchedulerException e) {
System.err.println("立即执行定时任务失败");
throw new ServiceException(BizExceptionEnum.SCHEDULE_RUN_ERROR);
}
}
/**
* 暂停任务
*/
public static void pauseJob(Scheduler scheduler, Long jobId) {
try {
scheduler.pauseJob(getJobKey(jobId));
} catch (SchedulerException e) {
System.err.println("暂停定时任务失败");
throw new ServiceException(BizExceptionEnum.SCHEDULE_PAUSE_ERROR);
}
}
/**
* 恢复任务
*/
public static void resumeJob(Scheduler scheduler, Long jobId) {
try {
scheduler.resumeJob(getJobKey(jobId));
} catch (SchedulerException e) {
System.err.println("恢复定时任务失败");
throw new ServiceException(BizExceptionEnum.SCHEDULE_RESUME_ERROR);
}
}
/**
* 删除定时任务
*/
public static void deleteScheduleJob(Scheduler scheduler, Long jobId) {
try {
scheduler.deleteJob(getJobKey(jobId));
} catch (SchedulerException e) {
System.err.println("删除定时任务失败");
throw new ServiceException(BizExceptionEnum.SCHEDULE_DELETE_ERROR);
}
}
}
ScheduleJobUtils工具类
@Slf4j
public class ScheduleJobUtils extends QuartzJobBean {
private ExecutorService service = Executors.newSingleThreadExecutor();
@Autowired
private ScheduleJobLogServiceImpl scheduleJobLogService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap()
.get(ConstCodes.JOB_PARAM_KEY);
//数据库保存执行记录
ScheduleJobLog jobLog = new ScheduleJobLog();
jobLog.setJobId(scheduleJob.getId());
jobLog.setName(scheduleJob.getName());
jobLog.setMethodName(scheduleJob.getMethodName());
jobLog.setParams(scheduleJob.getParams());
jobLog.setCreateTime(new Date());
//任务开始时间
long startTime = System.currentTimeMillis();
Byte zero = 0;
Byte one=1;
try {
//执行任务
log.info("任务准备执行,任务ID:" + scheduleJob.getId());
ScheduleRunnable task = new ScheduleRunnable(scheduleJob.getName(),
scheduleJob.getMethodName(), scheduleJob.getParams());
Future> future = service.submit(task);
future.get();
//任务执行总时长
long times = System.currentTimeMillis() - startTime;
jobLog.setTimes((int)times);
//任务状态 0:成功 1:失败
jobLog.setStatus(ConstCodes.SUCCESS);
log.info("任务执行完毕,任务ID:" + scheduleJob.getId() + " 总共耗时:" + times + "毫秒");
} catch (Exception e) {
log.error("任务执行失败,任务ID:" + scheduleJob.getId(), e);
//任务执行总时长
long times = System.currentTimeMillis() - startTime;
jobLog.setTimes((int)times);
//任务状态 0:成功 1:失败
jobLog.setStatus(ConstCodes.FAIL);
jobLog.setError(StringUtils.substring(e.toString(), 0, 2000));
}finally {
scheduleJobLogService.save(jobLog);
}
}
}
ScheduleRunnable实现Runnable
public class ScheduleRunnable implements Runnable{
private Object target;
private Method method;
private String params;
public ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException {
this.target = SpringContextHolder.getBean(beanName);
this.params = params;
if(StringUtils.isNotBlank(params)){
this.method = target.getClass().getDeclaredMethod(methodName, String.class);
}else{
this.method = target.getClass().getDeclaredMethod(methodName);
}
}
@Override
public void run() {
try {
ReflectionUtils.makeAccessible(method);
if(StringUtils.isNotBlank(params)){
method.invoke(target, params);
}else{
method.invoke(target);
}
}catch (Exception e) {
throw new ServiceException(BizExceptionEnum.SCHEDULE_RUN_ERROR);
}
}
}