基于quartz实现自定义时间的定时调度
关于定时调度,大部分是使用类似定时调度平台xxlJob或者spring的定时调度注解是实现的。其中xxlJob的控制基于web管理页面,需要手工配置。spring的调度注解@Scheduled需要配置值,无法运行时更改。所以他们都无法在运行时通过代码或者其他自定义的方式实现动态调度。
基于开源的一些代码,我整理了一个基于quartz实现自定义时间的定时调度的方法,下载代码链接:
https://download.csdn.net/download/ZHONGZEWEI/21117033
pom依赖
<dependencies>
<!-- 定时调度依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>
<!-- 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.0</version>
</dependency>
</dependencies>
定时调度执行计划实体类OperationPlan
OperationPlan用于配置定时调度的执行时间,执行名称,执行id等属性,可以自定义其他字段
@Data
@Builder
public class OperationPlan {
public static final String JOB_KEY = "JOB_KEY";
/**
* 标识id
*/
private Long id;
/**
* 任务名
*/
private String name;
/**
* cron表达式
*/
private String cronExpression;
}
实际执行调度任务的JobService
JobService用于传入OperationPlan,针对OperationPlan做一次调度任务,是实际调度任务的执行者。
package com.example.timer.service;
import cn.hutool.json.JSONUtil;
import com.example.timer.domain.OperationPlan;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class JobService {
public void executeOperationPlan(OperationPlan operationPlan) {
log.info("执行计划{}", JSONUtil.toJsonStr(operationPlan));
}
}
QuartzConfig注入相关bean到spring容器
/**
* 定时任务配置
* @author /
* @date 2019-01-07
*/
@Configuration
public class QuartzConfig {
/**
* 解决Job中注入Spring Bean为null的问题
*/
@Component("quartzJobFactory")
public static class QuartzJobFactory extends AdaptableJobFactory {
private final AutowireCapableBeanFactory capableBeanFactory;
public QuartzJobFactory(AutowireCapableBeanFactory capableBeanFactory) {
this.capableBeanFactory = capableBeanFactory;
}
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
/**
* 注入scheduler到spring
* @param quartzJobFactory /
* @return Scheduler
* @throws Exception /
*/
@Bean(name = "scheduler")
public Scheduler scheduler(QuartzJobFactory quartzJobFactory) throws Exception {
SchedulerFactoryBean factoryBean=new SchedulerFactoryBean();
factoryBean.setJobFactory(quartzJobFactory);
factoryBean.afterPropertiesSet();
Scheduler scheduler=factoryBean.getScheduler();
scheduler.start();
return scheduler;
}
}
QuartzManage处理定时调度的增删改等操作
package com.example.timer.timer;
import com.example.timer.domain.OperationPlan;
import com.example.timer.exp.BizException;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import static org.quartz.TriggerBuilder.newTrigger;
/**
* @author Zheng Jie
* @date 2019-01-07
*/
@Slf4j
@Component
public class QuartzManage {
private static final String JOB_NAME = "TASK_";
@Resource(name = "scheduler")
private Scheduler scheduler;
public void addJob(OperationPlan operationPlan) {
try {
deleteJob(operationPlan);
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(ExecutionJob.class).
withIdentity(JOB_NAME + operationPlan.getId()).build();
//通过触发器名和cron 表达式创建 Trigger
Trigger cronTrigger = newTrigger()
.withIdentity(JOB_NAME + operationPlan.getId())
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(operationPlan.getCronExpression()))
.build();
cronTrigger.getJobDataMap().put(OperationPlan.JOB_KEY, operationPlan);
//重置启动时间
((CronTriggerImpl) cronTrigger).setStartTime(new Date());
//执行定时任务
scheduler.scheduleJob(jobDetail, cronTrigger);
} catch (Exception e) {
log.error("创建定时任务失败", e);
throw BizException.wrap("创建定时任务失败");
}
}
/**
* 更新job cron表达式
*
* @param operationPlan /
*/
public void updateJobCron(OperationPlan operationPlan) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + operationPlan.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if (trigger == null) {
addJob(operationPlan);
trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
}
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(operationPlan.getCronExpression());
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//重置启动时间
((CronTriggerImpl) trigger).setStartTime(new Date());
trigger.getJobDataMap().put(OperationPlan.JOB_KEY, operationPlan);
scheduler.rescheduleJob(triggerKey, trigger);
} catch (Exception e) {
log.error("更新定时任务失败", e);
throw BizException.wrap("更新定时任务失败");
}
}
/**
* 删除一个job
*
* @param operationPlan /
*/
public void deleteJob(OperationPlan operationPlan) {
try {
JobKey jobKey = JobKey.jobKey(JOB_NAME + operationPlan.getId());
scheduler.pauseJob(jobKey);
scheduler.deleteJob(jobKey);
} catch (Exception e) {
log.error("删除定时任务失败", e);
throw BizException.wrap("恢复定时任务失败");
}
}
/**
* 恢复一个job
*
* @param operationPlan /
*/
public void resumeJob(OperationPlan operationPlan) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + operationPlan.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if (trigger == null) {
addJob(operationPlan);
}
JobKey jobKey = JobKey.jobKey(JOB_NAME + operationPlan.getId());
scheduler.resumeJob(jobKey);
} catch (Exception e) {
log.error("恢复定时任务失败", e);
throw BizException.wrap("恢复定时任务失败");
}
}
/**
* 立即执行job
*
* @param operationPlan /
*/
public void runJobNow(OperationPlan operationPlan) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + operationPlan.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if (trigger == null) {
addJob(operationPlan);
}
JobDataMap dataMap = new JobDataMap();
dataMap.put(OperationPlan.JOB_KEY, operationPlan);
JobKey jobKey = JobKey.jobKey(JOB_NAME + operationPlan.getId());
scheduler.triggerJob(jobKey, dataMap);
} catch (Exception e) {
log.error("定时任务执行失败", e);
throw BizException.wrap("定时任务执行失败");
}
}
/**
* 暂停一个job
*
* @param operationPlan /
*/
public void pauseJob(OperationPlan operationPlan) {
try {
JobKey jobKey = JobKey.jobKey(JOB_NAME + operationPlan.getId());
scheduler.pauseJob(jobKey);
} catch (Exception e) {
log.error("定时任务暂停失败", e);
throw BizException.wrap("定时任务暂停失败");
}
}
}
ExecutionJob接受调度任务并且基于反射调用jobService
package com.example.timer.timer;
import cn.hutool.core.exceptions.ExceptionUtil;
import com.example.timer.config.ThreadPoolExecutorUtil;
import com.example.timer.domain.OperationPlan;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 参考人人开源,https://gitee.com/renrenio/renren-security
*
* @author /
* @date 2019-01-07
*/
@Async
@Slf4j
public class ExecutionJob extends QuartzJobBean {
/**
* 该处仅供参考
*/
private final static ThreadPoolExecutor EXECUTOR = ThreadPoolExecutorUtil.getPoll();
@Override
public void executeInternal(JobExecutionContext context) {
OperationPlan operationPlan = (OperationPlan) context.getMergedJobDataMap().get(OperationPlan.JOB_KEY);
long startTime = System.currentTimeMillis();
try {
// 执行任务
log.info("任务开始执行,任务名称:" + operationPlan.getName());
QuartzRunnable task = new QuartzRunnable("jobService", "executeOperationPlan",
operationPlan);
Future<?> future = EXECUTOR.submit(task);
future.get();
long times = System.currentTimeMillis() - startTime;
log.info("任务执行完毕,任务名称:" + operationPlan.getName() + ", 执行时间:" + times + "毫秒");
} catch (Exception e) {
log.error("任务执行失败,任务名称:{},失败原因:{}" + operationPlan.getName(), ExceptionUtil.stacktraceToString(e));
}
}
}
项目启动时重新激活启用的定时任务
一般自定义的定时任务都是保存在数据库里面的,为了历史的调度任务不丢失,需要在应用启动时,重新激活启用的定时任务。
@Component
@Slf4j
public class JobRunner implements ApplicationRunner {
@Resource
private QuartzManage quartzManage;
/**
* 项目启动时重新激活启用的定时任务
*
* @param applicationArguments /
*/
@Override
public void run(ApplicationArguments applicationArguments) {
log.info("--------------------注入定时任务---------------------");
OperationPlan operationPlan = OperationPlan.builder().id(2L).name("每5秒执行一次").cronExpression("0/5 * * * * ? *").build();
quartzManage.addJob(operationPlan);
log.info("--------------------定时任务注入完成---------------------");
}
}
测试
测试1:测试历史的,随项目启动时重新激活启用的定时任务
测试2:新增每秒执行一次的定时任务
我们通过新增一个接口job,通过调用接口的方式去动态增加调度任务
@RestController
@RequestMapping("/job")
public class JobController {
@Resource
QuartzManage quartzManage;
@PostMapping()
public String add(@RequestBody OperationPlan operationPlan) {
quartzManage.addJob(operationPlan);
return "动态添加自定义时间定时任务成功";
}
}