quartz-动态任务开发
介绍:根据任务数据不同,执行不同的内容
案例主要技术栈:MybatisPlus + MySQL
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
解决job实现类无法注入spring管理的bean
开发CustomJobFactory
类,继承AdaptableJobFactory
类
/**
* @author riKylinz
* @description 自定义job工厂,用于解决job实现类无法注入spring管理的bean 与QuartzConfig类连用
* 并把SchedulerFactoryBean的job工厂设置成 自定义job工厂(CustomJobFactory)
* @date 2022/9/13 11:51
*/
@Component
public class CustomJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory autowireCapableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类方法 拿到属于Quartz的job实现类的实例
Object jobInstance = super.createJobInstance(bundle);
//把实例注入spring容器中
autowireCapableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
开发quartz配置类,把自定义工厂类添加到SchedulerFactoryBean
中
/**
* @author riKylinz
* @description Quartz配置类
* @date 2022/9/13 11:50
*/
@Configuration
public class QuartzConfig {
@Autowired
private CustomJobFactory customJobFactory;
@Bean
public SchedulerFactoryBean schedulerFactoryBean(){
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setJobFactory(customJobFactory);
return schedulerFactoryBean;
}
}
开发job实现类
/**
* @author rikylinz
* @description 自定义job
* @date 2022/9/13 11:21
*/
@Component
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class CustomJob extends LoggerObject implements Job {
@Autowired
private ScheduleJobService scheduleJobService;//自定义的任务对象,后续有代码展示
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDataMap map = jobExecutionContext.getMergedJobDataMap();
String beanName = map.getString("beanName");
String num = map.getString("num");
//定时任务状态 改为执行中
scheduleJobService.update(new UpdateWrapper<ScheduleJobPo>()
.eq("num", num).set("status",1));
//实际执行的内容
int status;
String failed = null;
CommonResult commonResult = null;
try {
Timer timer = BeanUtils.getBean(beanName, Timer.class);
commonResult = timer.timer(map);
status = 3;
} catch (Exception e) {
logger.error("定时任务执行失败,num:{}", num,e);
status = 4;
failed = StringUtils.isEmpty(e.getMessage()) ? "定时任务执行失败" : e.getMessage();
}
//定时任务状态 改为执行完成
UpdateWrapper<ScheduleJobPo> updateWrapper = new UpdateWrapper<ScheduleJobPo>()
.eq("num", num).set("status", status);
if(status == 3){
updateWrapper.set("success_message", JSON.toJSONString(commonResult.getData()));
}
if(status == 4){
updateWrapper.set("error_message",failed);
}
scheduleJobService.update(updateWrapper);
}
}
开发任务类接口
/**
* 自定义定时任务需要实现的接口
* 注意:Map中必须带有num参数,该参数是执行任务对象的编号数据(该数据可以是任意类型的唯一性数据)
*/
public interface Timer {
/**
* 实现方式内容就是定时任务执行的内容
* @param map
* @return JSON数据
*/
CommonResult timer(JobDataMap map);
}
开发任务列表枚举
@Getter
@AllArgsConstructor
public enum TimerEnum {
DEMAND("customTimer","执行任务内容描述");
private String beanName;//beanName
private String message;//描述
}
开发定时任务配置类
/**
* @author riKylinz
* @description 定时任务配置类
* @date 2022/9/13 13:45
*/
@Component
public class SchedulerConfig implements InitializingBean {
@Autowired
private SchedulerUse schedulerUse;//工具类
@Override
public void afterPropertiesSet() throws Exception {
//启动定时任务
schedulerUse.start();
}
@PostConstruct
public void scheduleResume(){
//项目启动时,清空定时器中的所有任务、恢复未执行以及执行中的任务
schedulerUse.scheduleResume();
}
}
开发定时任务使用工具类
/**
* @author riKylinz
* @description 定时任务使用类
* @date 2022/9/13 13:06
*/
@Component
public class SchedulerUse extends LoggerObject {
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
@Autowired
private ScheduleJobService scheduleJobService;
/**
* 开启定时任务-项目启动时执行
*/
public void start(){
Scheduler scheduler = schedulerFactoryBean.getScheduler();
try {
scheduler.start();
logger.info("定时器启动成功!!!!");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 添加定时任务
* @param jobDetail jobDetail
* @param trigger 触发器
* @throws SchedulerException
*/
public void scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.scheduleJob(jobDetail,trigger);
logger.info("定时任务添加成功,jobName:{}",jobDetail.getKey().getName());
}
/**
* 获取 JobDetail
* @param scheduleJobPo 定时任务对象
* @param jobDataMap 数据对象(在定时任务内容中使用的数据)
* @param beanName 定时任务内容对象
* @return JobDetail
*/
public JobDetail getJobDetail(ScheduleJobPo scheduleJobPo, JobDataMap jobDataMap, String beanName){
return JobBuilder.newJob(CustomJob.class)
.withIdentity(scheduleJobPo.getName(),scheduleJobPo.getGroupName())
.usingJobData(jobDataMap)
.usingJobData("beanName",beanName)
.build();
}
/**
* 获取 SimpleTrigger 触发器
* @param scheduleJobPo 定时任务对象
* @return Trigger
*/
public Trigger getSimpleTrigger(ScheduleJobPo scheduleJobPo){
return TriggerBuilder.newTrigger()
.withIdentity(scheduleJobPo.getName(),scheduleJobPo.getGroupName())
.startAt(new Date(scheduleJobPo.getStartAt().getTime()))
.build();
}
/**
* 获取 CronTrigger 触发器
* @param scheduleJobPo 定时任务对象
* @return Trigger
*/
public Trigger getCronTrigger(ScheduleJobPo scheduleJobPo){
return TriggerBuilder.newTrigger()
.withIdentity(scheduleJobPo.getName(),scheduleJobPo.getGroupName())
.withSchedule(CronScheduleBuilder.cronSchedule(scheduleJobPo.getCron()))
.build();
}
/**
* 删除定时器中所有任务
*/
public void scheduleDeleteAll(){
Scheduler scheduler = schedulerFactoryBean.getScheduler();
try {
Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.anyJobGroup());
for (JobKey jobKey : jobKeys) {
String name = jobKey.getName();
String group = jobKey.getGroup();
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if(jobDetail == null){
return ;
}
scheduler.deleteJob(jobKey);
//修改数据库
scheduleJobService.update(new UpdateWrapper<ScheduleJobPo>().eq("name",name)
.eq("group_name",group).set("status",3));
}
//根据
} catch (Exception e) {
logger.error("删除之前存留定时任务失败,原因:{}",e);
throw new RuntimeException(e);
}
}
/**
* 恢复未执行的定时任务
*/
public void scheduleResume(){
try {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
this.scheduleDeleteAll();
//查询定时任务表 把未执行以及执行中的任务加入
List<ScheduleJobPo> list = scheduleJobService.list(new QueryWrapper<ScheduleJobPo>()
.in("status", 0,1));
for (ScheduleJobPo scheduleJobPo : list) {
JobDataMap jobDataMap = JSONObject.parseObject(scheduleJobPo.getJobDataMap(), JobDataMap.class);
JobDetail jobDetail = this.getJobDetail(scheduleJobPo, jobDataMap, scheduleJobPo.getTimerName());
Trigger trigger;
if(!StringUtils.isEmpty(scheduleJobPo.getCron())){
trigger = this.getCronTrigger(scheduleJobPo);
}else{
trigger = this.getSimpleTrigger(scheduleJobPo);
}
logger.info("恢复定时任务!num:{}",scheduleJobPo.getNum());
scheduler.scheduleJob(jobDetail,trigger);
}
} catch (Exception e) {
logger.error("恢复未执行的定时任务失败,原因:{}",e);
throw new RuntimeException(e);
}
}
/**
* 暂停定时任务
* @param jobKey jobKey
*/
public void schedulePause(JobKey jobKey) {
try {
String name = jobKey.getName();
String group = jobKey.getGroup();
ScheduleJobPo scheduleJobPo = scheduleJobService.getOne(new QueryWrapper<ScheduleJobPo>().eq("name", name)
.eq("group_name", group).in("status", 0, 1));
if (scheduleJobPo == null){
logger.info("定时任务不存在,jobKey:{}", JSON.toJSONString(jobKey));
throw new RuntimeException("定时任务不存在");
}
Scheduler scheduler = schedulerFactoryBean.getScheduler();
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if (jobDetail == null)
return;
scheduler.pauseJob(jobKey);
}catch (Exception e){
logger.error("暂停定时任务失败,jobKey:{},原因:{}",jobKey,e);
}
}
}
使用案例
-
先实现timer接口,完成任务执行内容
-
把实现类注入到spring容器中,并自定义bean名称
-
把自定义bean名称配置到TimerEnum枚举中
-
使用
//scheduleJobPo需要提前组装好并存入数据库中 JobDetail jobDetail = schedulerUse.getJobDetail(scheduleJobPo, jobDataMap, TimerEnum.DEMAND.getBeanName()); Trigger simpleTrigger = schedulerUse.getSimpleTrigger(scheduleJobPo); try { schedulerUse.scheduleJob(jobDetail,simpleTrigger); } catch (SchedulerException e) { logger.error("定时任务添加失败,参数列表:{},原因:{}",JSON.toJSONString(demandPublishDto),e); throw new RuntimeException("定时任务添加失败"); }
ScheduleJob
实体类,实际根据自身项目而定
/**
* @author riKylinz
* @description 定时任务表
* @date 2022/9/13 13:58
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName("schedule_job")
public class ScheduleJobPo {
@TableId(value = "id",type = IdType.AUTO)
private Integer id;//主键id
@TableField(value = "num")
private String num;//编号,存储与其他表关联的唯一字段
@TableField(value = "name")
private String name;//定时任务 唯一标识
@TableField(value = "group_name")
private String groupName;//组名
@TableField(value = "start_at")
private Timestamp startAt;//执行时间
@TableField(value = "cron")
private String cron;//cron表达式
@TableField(value = "timer_name")
private String timerName;//任务实例名称
@TableField(value = "job_data_map")
private String jobDataMap;//参数map
@TableField(value = "status")
private Integer status;//状态 0:未执行 1:执行中 2:暂停 3:已完成 4:执行失败 -1:已删除
@TableField(value = "success_message")
private String successMessage;//执行成功信息
@TableField(value = "error_message")
private String errorMessage;//执行失败信息
@TableField(value = "create_time")
private Timestamp createTime;//创建时间
@TableField(value = "update_time")
private Timestamp updateTime;//修改时间
public void setJobDataMap(String jobDataMap) {
JobDataMap jobDataMap1 = JSONObject.parseObject(jobDataMap, JobDataMap.class);
if(!jobDataMap1.containsKey("num")){
throw new RuntimeException("num参数必填,数据任意类型,但唯一");
}
this.jobDataMap = jobDataMap;
}
}