最近项目需要使用动态的定时任务,而且还需要在后台进行可视化管理动态任务。这里其实有两个实现思路:第一个就是xxlJob动态任务、第二个就是Quartz框架,这两个的技术路线与框架都是很成熟的技术,都是可以提供动态可支持的定时任务,而且都支持分布式开发。大体的区别就是在于:xxlJob对于分布式和微服务的模式更加的友好,自身就提供了可管理任务的可视化界面,而且配置与使用以及开大起来都是相对便捷一些。Quartz其实也可以用于分布式,但是微服务的话就不推荐使用了。如果你的项目是模块化项目或者是多个项目组成的那就可以使用Quartz这个技术,我个人认为用起来比较舒服,能满足动态定时的基本操作,实现起来也不是很难。接下来就介绍一下springboot怎么整合Quartz实现动态定时任务。
第一步:依然是常规化的导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
第二步:在.yml文件中增加配置
quartz:
# job-store-type: jdbc
# properties:
# org:
# quartz:
# scheduler:
# instanceName: clusteredScheduler
# instanceId: AUTO
# jobStore:
# selectWithLockSQL: SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?
# class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
# tablePrefix: QRTZ_
# isClustered: false # 打开集群配置
# clusterCheckinInterval: 2000 # 设置集群检查间隔20s
# useProperties: false
# threadPool:
# class: org.quartz.simpl.SimpleThreadPool
# threadCount: 10
# threadPriority: 5
# threadsInheritContextClassLoaderOfInitializingThread: true
导入之后把注释去掉,其他的都不用改,需要注意一点tablePrefix这个配置,这个是Quartz需要用到的表的表名前缀,最好不要改,当前你想改也是可以的,但是改起来会很麻烦,要改很多的文件。
第三步:导入需要用到的表(直接从资源里面下载,导入到数据库中即可)
简单介绍一下表的作用
qrtz开头的表示Quartz动态任务调用时的必须要用到的表,千万不要随意增减字段,有调度任务表,时间表达式存储表,正在运行任务的表等等。下面的这个sys_schedule表,里面的字段不要删,但是你可以增加你想要的字段。这里面记录了你自己系统里面发起任务的数据。相当于前端form表单提交的任务数据就会保存到这个表里面,导入这个表后,使用工具生成最基本的实体类,controller类等基本类,跟平时开发是一样的。但需要特别注意的是,我们需要设置数据库忽略大小写的配置,在mysql配置文件中增加一个属性就行,网上很多。
第四步:代码配置Task任务:
@DisallowConcurrentExecution
public abstract class Task implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
run();
System.out.println("任务在" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()) + " 时运行");
}
public abstract void run();
}
这是一个抽象类,实现了Job任务类,当前端调用你的controller层方法时,任务触发会先执行这个execute这个方法,这个方法里面会先用这个run方法,关键点就是这个run的抽象方法是我们自己定义的。以后你想实现什么定时任务,就需要要继承这个Task类并且实现这个run方法,就可调用你的代码了,例如下面:
@DisallowConcurrentExecution
public class TestSchedule extends Task {
@Override
public void run() {
System.out.println("=============定时任务被调用了====================");
}
}
在这个实现了父类的run方法里面写你的具体逻辑。每写一个定时任务类,你都要继承这个Task类,实现run方法。
第五步:核心任务调度的contorller与services层的调用任务代码,直接粘进行用即可。
/**
* 暂停任务
*/
@PostMapping("stop")
public ResponseJson stop(SysSchedule scheduleJob) {
sysScheduleService.stopJob(scheduleJob);
return buildSuccessResult("暂停成功!");
}
/**
* 立即运行一次
*/
@PostMapping("startNow")
public ResponseJson stratNow(SysSchedule scheduleJob) {
sysScheduleService.startNowJob(scheduleJob);
return buildSuccessResult("运行成功");
}
/**
* 恢复
*/
@PostMapping("resume")
public ResponseJson resume(SysSchedule scheduleJob, RedirectAttributes redirectAttributes) {
sysScheduleService.restartJob(scheduleJob);
//恢复之后,立即触发一次激活定时任务,不然定时任务有可能不会执行
sysScheduleService.startNowJob(scheduleJob);
return buildSuccessResult("恢复成功");
}
/**
* 批量删除定时任务
*/
@DeleteMapping("delete")
public ResponseJson deleteAll(String ids, RedirectAttributes redirectAttributes) {
String idArray[] = ids.split(",");
for (String id : idArray) {
sysScheduleService.deleteByIds(sysScheduleService.getById(id));
}
return buildSuccessResult("删除成功");
}
@Transactional(readOnly = false)
@Override
public void stopJob(SysSchedule SysSchedule) {
JobKey key = new JobKey(SysSchedule.getName(), SysSchedule.getSchGroup());
try {
scheduler.pauseJob(key);
SysSchedule.setStatus("0");
super.updateById(SysSchedule);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
@Transactional(readOnly = false)
@Override
public void startNowJob(SysSchedule SysSchedule) {
JobKey key = new JobKey(SysSchedule.getName(), SysSchedule.getSchGroup());
try {
scheduler.triggerJob(key);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
/**
* 恢复任务
*/
@Transactional(readOnly = false)
@Override
public void restartJob(SysSchedule SysSchedule) {
JobKey key = new JobKey(SysSchedule.getName(), SysSchedule.getSchGroup());
try {
scheduler.resumeJob(key);
SysSchedule.setStatus("1");
super.updateById(SysSchedule);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
@Transactional(readOnly = false)
@Override
public void saveJob(SysSchedule SysSchedule) {
SysSchedule t = this.getById(SysSchedule.getId());
if (Objects.nonNull(t)) {
JobKey key = new JobKey(t.getName(), t.getSchGroup());
try {
scheduler.deleteJob(key);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
this.add(SysSchedule);
super.save(SysSchedule);
}
@Override
public void deleteByIds(SysSchedule scheduleJob) {
JobKey key = new JobKey(scheduleJob.getName(), scheduleJob.getSchGroup());
try {
scheduler.deleteJob(key);
super.removeById(scheduleJob);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
@Transactional(readOnly = false)
public void add(SysSchedule SysSchedule) {
Class job = null;
try {
job = Class.forName(SysSchedule.getClassname());
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}
JobDetail jobDetail = JobBuilder.newJob(job).withIdentity(SysSchedule.getName(), SysSchedule.getSchGroup())
.build();
jobDetail.getJobDataMap().put("SysSchedule", SysSchedule);
// 表达式调度构建器(可判断创建SimpleScheduleBuilder)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(SysSchedule.getExpression());
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(SysSchedule.getName(), SysSchedule.getSchGroup()).withSchedule(scheduleBuilder).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
JobKey key = new JobKey(SysSchedule.getName(), SysSchedule.getSchGroup());
if (SysSchedule.getStatus().equals("0")) {
scheduler.pauseJob(key);
} else {
scheduler.resumeJob(key);
}
} catch (SchedulerException e) {
e.printStackTrace();
}
}
/**
* 获取所有JobDetail
*
* @return 结果集合
*/
public List<JobDetail> getJobs() {
try {
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
List<JobDetail> jobDetails = new ArrayList<JobDetail>();
for (JobKey key : jobKeys) {
jobDetails.add(scheduler.getJobDetail(key));
}
return jobDetails;
} catch (SchedulerException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取所有计划中的任务
*
* @return 结果集合
*/
public List<SysSchedule> getAllSysSchedule() {
List<SysSchedule> SysScheduleList = new ArrayList<SysSchedule>();
;
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
try {
Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
for (JobKey jobKey : jobKeys) {
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
for (Trigger trigger : triggers) {
SysSchedule SysSchedule = new SysSchedule();
SysSchedule.setName(jobKey.getName());
SysSchedule.setSchGroup(jobKey.getGroup());
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
SysSchedule.setStatus(triggerState.name());
//获取要执行的定时任务类名
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
SysSchedule.setClassname(jobDetail.getJobClass().getName());
//判断trigger
if (trigger instanceof SimpleTrigger) {
SimpleTrigger simple = (SimpleTrigger) trigger;
SysSchedule.setExpression("重复次数:" + (simple.getRepeatCount() == -1 ?
"无限" : simple.getRepeatCount()) + ",重复间隔:" + (simple.getRepeatInterval() / 1000L));
SysSchedule.setDescription(simple.getDescription());
}
if (trigger instanceof CronTrigger) {
CronTrigger cron = (CronTrigger) trigger;
SysSchedule.setExpression(cron.getCronExpression());
SysSchedule.setDescription(cron.getDescription() == null ? ("触发器:" + trigger.getKey()) : cron.getDescription());
}
SysScheduleList.add(SysSchedule);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return SysScheduleList;
}
/**
* 获取所有运行中的任务
*
* @return 结果集合
*/
public List<SysSchedule> getAllRuningSysSchedule() {
List<SysSchedule> SysScheduleList = null;
try {
List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
SysScheduleList = new ArrayList<SysSchedule>(executingJobs.size());
for (JobExecutionContext executingJob : executingJobs) {
SysSchedule SysSchedule = new SysSchedule();
JobDetail jobDetail = executingJob.getJobDetail();
JobKey jobKey = jobDetail.getKey();
Trigger trigger = executingJob.getTrigger();
SysSchedule.setName(jobKey.getName());
SysSchedule.setSchGroup(jobKey.getGroup());
//SysSchedule.setDescription("触发器:" + trigger.getKey());
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
SysSchedule.setStatus(triggerState.name());
//获取要执行的定时任务类名
SysSchedule.setClassname(jobDetail.getJobClass().getName());
//判断trigger
if (trigger instanceof SimpleTrigger) {
SimpleTrigger simple = (SimpleTrigger) trigger;
SysSchedule.setExpression("重复次数:" + (simple.getRepeatCount() == -1 ?
"无限" : simple.getRepeatCount()) + ",重复间隔:" + (simple.getRepeatInterval() / 1000L));
SysSchedule.setDescription(simple.getDescription());
}
if (trigger instanceof CronTrigger) {
CronTrigger cron = (CronTrigger) trigger;
SysSchedule.setExpression(cron.getCronExpression());
SysSchedule.setDescription(cron.getDescription());
}
SysScheduleList.add(SysSchedule);
}
} catch (SchedulerException e) {
e.printStackTrace();
}
return SysScheduleList;
}
/**
* 获取所有的触发器
*
* @return 结果集合
*/
public List<SysSchedule> getTriggersInfo() {
try {
GroupMatcher<TriggerKey> matcher = GroupMatcher.anyTriggerGroup();
Set<TriggerKey> Keys = scheduler.getTriggerKeys(matcher);
List<SysSchedule> triggers = new ArrayList<SysSchedule>();
for (TriggerKey key : Keys) {
Trigger trigger = scheduler.getTrigger(key);
SysSchedule SysSchedule = new SysSchedule();
SysSchedule.setName(trigger.getJobKey().getName());
SysSchedule.setSchGroup(trigger.getJobKey().getGroup());
SysSchedule.setStatus(scheduler.getTriggerState(key) + "");
if (trigger instanceof SimpleTrigger) {
SimpleTrigger simple = (SimpleTrigger) trigger;
SysSchedule.setExpression("重复次数:" + (simple.getRepeatCount() == -1 ?
"无限" : simple.getRepeatCount()) + ",重复间隔:" + (simple.getRepeatInterval() / 1000L));
SysSchedule.setDescription(simple.getDescription());
}
if (trigger instanceof CronTrigger) {
CronTrigger cron = (CronTrigger) trigger;
SysSchedule.setExpression(cron.getCronExpression());
SysSchedule.setDescription(cron.getDescription());
}
triggers.add(SysSchedule);
}
return triggers;
} catch (SchedulerException e) {
e.printStackTrace();
}
return null;
}
第六步:开发可视化界面,这个界面以及表单就是根据sys_schedule这个表生成的。具体的我把图贴出来供大家参考:
具体界面随开发进行变化,但是基本字段就这样,如果你还想加字段,也可以加。表单中的定时规则大家可以再往上找一个cron表达式的生成页面,让用户去选,然后生成表达式。在controller中,调用每一个任务时,参数都是SysSchedule这个实体类,把数据中的每个字段都让前端传到后端接收,后端只需要调用相应的任务方法即可。
当你测试或者保存的任务的时候,sys_schedule这个表就会多一条任务数据,相应的qrtz相关表里也会多出数据,测试是否集成成功了,你就测试新增成功后qrtz相关表里是否多了相应数据以及表达式的数据。
如果有什么问题,可以留言一起讨论