1. 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2. 逻辑说明
- 在容器(TaskContext)中每隔N秒扫描数据库中的任务配置
- 根据配置生成任务。任务不存在则创建;存在则更新
主要的文件如下:
文件名 | 功能 |
TaskConfig | 生成scheduler,并将其注入到spring容器中 |
MyJobFactory | TaskConfig所依赖 |
QuartzManager | 封装了定时任务的操作 |
TaskContext | 任务容器,主要是定时扫描任务配置,并生成/更新任务 |
TaskMapper | 配置查询(Mybatis) |
3. 源码
3.1 MyJobFactory
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;
/**
* @author pp_lan
* @date 2024/1/2
*/
@Component
public class MyJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object jobInstance = super.createJobInstance(bundle);
// 注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
3.2 TaskConfig
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
/**
* @author pp_lan
* @date 2024/1/2
*/
@Configuration
@DependsOn("myJobFactory")
public class TaskConfig {
@Autowired
private MyJobFactory myJobFactory;
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setJobFactory(myJobFactory);
return schedulerFactoryBean;
}
@Bean
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}
3.3 QuartzManager
import com.alibaba.druid.util.StringUtils;
import org.quartz.*;
import org.quartz.impl.calendar.CronCalendar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author pp_lan
* @date 2024/1/2
*/
@Configuration
public class QuartzManager {
private static final Logger LOGGER = LoggerFactory.getLogger(QuartzManager.class);
@Autowired
private Scheduler scheduler;
@Autowired
private List<Job> taskList;
private Map<String, Job> taskMap;
@PostConstruct
public void init() throws ParseException, SchedulerException {
this.taskMap = taskList.stream().collect(Collectors.toMap(t -> t.getClass().getSimpleName(), t -> t));
// 此处配置不执行任务的时间段
CronCalendar cronCalendar = new CronCalendar("0 0/1 * * * ?");
this.scheduler.addCalendar("minute", cronCalendar, false, false);
}
public void startJobTask(String name, String group, String cron) throws SchedulerException {
JobKey jobKey = new JobKey(name, group);
if (this.scheduler.checkExists(jobKey)) {
// 任务修改
modifyJob(name, group, cron);
} else {
// 任务新增
if (taskMap.containsKey(name)) {
// 执行任务
Class<? extends Job> taskClazz = taskMap.get(name).getClass();
JobDetail jobDetail = JobBuilder.newJob(taskClazz).withIdentity(name, group).build();
// 执行时间正则
CronScheduleBuilder cronBuilder = CronScheduleBuilder.cronSchedule(cron);
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(name, group)
.withSchedule(cronBuilder)
// 排除掉的执行时间
.modifiedByCalendar("minute")
.build();
scheduler.scheduleJob(jobDetail, cronTrigger);
} else {
LOGGER.debug("任务没有配置执行配置{}", name);
}
}
}
public String getJobInfo(String name, String group) throws SchedulerException {
TriggerKey triggerKey = new TriggerKey(name, group);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
return new StringBuilder("time:").append(trigger.getCronExpression()).append("\t state:")
.append(scheduler.getTriggerState(triggerKey).name()).toString();
}
/**
* 修改
*
* @param name
* @param group
* @param cron
* @return
* @throws SchedulerException
*/
public boolean modifyJob(String name, String group, String cron) throws SchedulerException {
TriggerKey triggerKey = new TriggerKey(name, group);
Trigger trigger = scheduler.getTrigger(triggerKey);
if (trigger == null) {
LOGGER.info("未存在的触发器[{}-{}]", name, group);
return false;
}
String oldCron = ((CronTrigger) trigger).getCronExpression();
if (!StringUtils.equals(cron, oldCron)) {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
CronTrigger newTrigger = TriggerBuilder.newTrigger().withIdentity(name, group).withSchedule(cronScheduleBuilder)
.build();
Date date = scheduler.rescheduleJob(triggerKey, newTrigger);
return date != null;
}
return false;
}
public void pauseAll() throws SchedulerException {
this.scheduler.pauseAll();
}
public void pause(String name, String group) throws SchedulerException {
JobKey jobKey = new JobKey(name, group);
JobDetail jobDetail = this.scheduler.getJobDetail(jobKey);
if (jobDetail == null) {
return;
}
this.scheduler.pauseJob(jobKey);
}
/**
* 恢复
*
* @throws SchedulerException
*/
public void resumeAllJob()throws SchedulerException{
scheduler.resumeAll();
}
/**
* 删除任务
*
* @param name
* @param group
* @throws SchedulerException
*/
public void delete(String name, String group) throws SchedulerException {
JobKey jobKey = new JobKey(name, group);
JobDetail jobDetail = this.scheduler.getJobDetail(jobKey);
if (jobDetail == null) {
return;
}
this.scheduler.deleteJob(jobKey);
}
}
3.4 TaskContext
import com.hz.job.bean.TaskConst;
import com.hz.job.bean.TaskDto;
import com.hz.job.mapper.TaskMapper;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author pp_lan
* @date 2024/1/2
*/
@Component
@DependsOn(value = {"quartzManager"})
public class TaskContext {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskContext.class);
@Autowired
private QuartzManager quartzManager;
@Autowired
private TaskMapper taskMapper;
@Scheduled(fixedRate = 3000)
public void update() {
try {
List<TaskDto> taskDtos = taskMapper.taskInfo();
for (TaskDto taskDto : taskDtos) {
this.quartzManager.startJobTask(taskDto.getTaskName(), TaskConst.GROUP.SYS.name(), taskDto.getCron());
}
} catch (SchedulerException e) {
LOGGER.error("初始化定时任务异常", e);
}
}
}
3.5 TaskMapper
import com.hz.job.bean.TaskDto;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface TaskMapper {
@Select("select id, task_name, cron from t_scheduler where is_delete = 0")
List<TaskDto> taskInfo();
}
3.6 任务示例
3.6.1 SimpleTask
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @author pp_lan
* @date 2024/1/2
*/
@Component
public class SimpleTask implements Job {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleTask.class);
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
LOGGER.info("执行简单任务");
}
}
3.6.2 WeatherTask
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @author pp_lan
* @date 2024/1/2
*/
@Component
public class WeatherTask implements Job {
private static final Logger LOGGER = LoggerFactory.getLogger(WeatherTask.class);
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
LOGGER.info("天气预报");
}
}
3.7 其他类
/**
* @author pp_lan
* @date 2024/1/2
*/
public class TaskConst {
public enum GROUP {
SYS;
}
}
/**
* @author pp_lan
* @date 2024/1/2
*/
public class TaskDto {
private int id;
private String taskName;
private String cron;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getCron() {
return cron;
}
public void setCron(String cron) {
this.cron = cron;
}
@Override
public String toString() {
return "TaskDto{" +
"id=" + id +
", taskName='" + taskName + '\'' +
", cron='" + cron + '\'' +
'}';
}
}
4. 相关数据库表及数据
CREATE TABLE public.t_scheduler (
id int4 NOT NULL,
task_name varchar(32) NOT NULL,
cron varchar(32) NOT NULL,
is_delete int2 NOT NULL DEFAULT 0,
CONSTRAINT pk_t_scheduler_id PRIMARY KEY (id)
);
INSERT INTO public.t_scheduler (id,task_name,cron,is_delete) VALUES
(1,'SimpleTask','0/4 * * * * ?',0),
(2,'WeatherTask','0/4 * * * * ?',0);
5. 执行结果
5.1 结果截图
5.2 说明
-
在修改数据库配置的cron后支持动态更改执行周期;
-
3.3QuartzManager中init方法,配置了定时任务不执行的时间段。因此,截图中整分时间(2024-01-02 15:00:00)没有执行这两个任务。