本文主要讲解以下几个方面:
1.定时任务的定义及其常见的模式
2.springboot集成quart实例
3.中途会遇到的一些问题
一、定时任务的定义及其常见的模式
1)定时任务的定义
首先要明白的是,定时任务在系统中的表现形式和在我们口中常说的定时是不一样的:
口中的定时:在明天的八点把设备打开,晚上八点把设备关掉,这个月每天都这样。
系统的定时:上述的定时在系统中就会表现为两个任务:
2019-05-15到2019-05-31,在8点把设备打开,间隔24小时(隔天)再操作一次;
2019-05-15到2019-05-31,在20点把设备关闭,间隔24小时(隔天)再操作一次。
也就是说系统的实现需要肢解到单个操作,然后看单个操作的规律来设定任务。(讲这个是因为涉及到后面的Cron表达式,一个时间段的可能需要肢解为多个任务)
2)常见的几种定时任务方式
一种是while无限循环,隔一定时间执行一次;
一种是Timer,支持一次性调度和循环调度,循环调度有分为固定速率调度(fixRate)和固定时延调度(fixDelay),能支持很多的应用了;它是单线程的,原理如下:
Timer 类里包含一个任务队列和一个异步轮训线程:
任务队列里容纳了所有待执行的任务,所有的任务将会在这一个异步线程里执行;
轮训线程会每次轮训出时间点最近的并且到点的任务来执行
可参考:https://blog.51cto.com/13732225/2331530
一种是ScheduledExecutorService,这个是用线程池了,可以很灵活的去设定第一次执行任务delay时间等,
可参考:https://www.cnblogs.com/maoniu602/p/3900587.html
一种就是quartz,基本上具备了上述的各种优点,还实现了任务和触发器的多对多的关系,可以动态的添加删除定时任务,另外很好的支撑集群调度;它的核心如下:
1个job可对应多个Trigger
二、springboot集成quart实例
主要的结构如下:
netak可放到model目录中。
这是我的线程启动类,用来替代SchedulerListener.java,大家没有其他线程也可以用SchedulerListener,自己看着放。
1.QuartzScheduler.java:涉及的是基本的配置,以后的动态修改方法。这个是核心,大家好好看看注解。
import java.util.Date;
import java.util.List;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzScheduler {
@Autowired
private Scheduler scheduler;
@Autowired
private NeTaskService neTaskService;
/**
* 开始执行所有任务
*/
public void startJob() throws SchedulerException {
//启动时进行查库将任务添加至job
List netaskList = neTaskService.findAllByNeIdAndInUse();
List zctaskList = neTaskService.findAllByIpAndInUse();
//添加任务至调度器scheduler
startJob1(scheduler,netaskList);
//调度任务开始执行
// scheduler.start();
startJob2(scheduler,zctaskList);
scheduler.start();
}
/*
* 重启所有任务
*/
public void restartJob() throws SchedulerException, InterruptedException {
//不可以用shutdown,也不需要停止,直接清除,然后启动
// scheduler.shutdown();
// scheduler.pauseAll();
scheduler.clear();
this.startJob();
}
/**
* title:
* mentality:
* @throws
* @param scheduler2
* @param zctaskList
*/
private void startJob2(Scheduler scheduler2, List zctaskList) throws SchedulerException{
// TODO Auto-generated method stub
}
/**
* title:计划任务1
* mentality:
* @throws
* @param scheduler2
* @param netaskList
*/
private void startJob1(Scheduler scheduler2, List netaskList) throws SchedulerException{
// TODO Auto-generated method stub
// 通过JobBuilder构建JobDetail实例,JobDetail规定只能是实现Job接口的实例
// JobDetail 是具体Job实例
for(NeTask netask : netaskList){
JobDetail jobDetail = JobBuilder.newJob(NeTaskJob.class)//不同的业务,增加不同的.class
.withIdentity(netask.getId().toString(), netask.getStationId()+netask.getNeId())
.build();
jobDetail.getJobDataMap().put("id",netask);
List cronList = CronUtil.getCronByTask(netask);
// for(Cron cron : cronList ){
for(int i = 0;i< cronList.size();i++){
// 基于表达式构建触发器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(
//下面的cron你可以直接写个cron表达式来做验证,入:每隔5秒执行一次:*/5 * * * * ?
cronList.get(i).getCron()
);
// CronTrigger表达式触发器 继承于Trigger
// TriggerBuilder 用于构建触发器实例
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
//若一个jobdetail有多个trigger,则需要注意命名规则,便于后面修改任务
// .withIdentity(netask.getNeId().toString(), netask.getStationId())
.forJob(jobDetail)
.withIdentity(netask.getId() + CronUtil.cronEndId[i], netask.getStationId()+netask.getNeId())
.withSchedule(cronScheduleBuilder).build();
// scheduleJob该接口的作用是在将任务加入Quartz的同时绑定一个Trigger,Quartz会在加入该任务后自动设置Trigger的JobName与JobGroup为该JobDetail的name与group
if(i==0){
scheduler2.scheduleJob(jobDetail, cronTrigger);//第一次必须有jobdetail
}else{
scheduler2.scheduleJob(cronTrigger);
}
//rescheduleJob(String, String, Trigger) 替换一个指定的Trigger, 即解除指定Trigger与任务的绑定,并将新的Trigger与任务绑定,Quartz会自动调整新Trigger的JobName与JobGroup,而旧的Trigger将被移除
//Scheduler#triggerJob(String, String) 创建一个立即触发的Trigger,并将其与name与group指定的任务绑定
}
}
}
/**
* 获取Job信息
*
* @param name
* @param group
* @return
* @throws SchedulerException
*/
public String getJobInfo(String name, String group) throws SchedulerException {
TriggerKey triggerKey = new TriggerKey(name, group);
CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
return String.format("time:%s,state:%s", cronTrigger.getCronExpression(),
scheduler.getTriggerState(triggerKey).name());
}
/**
* 修改某个任务的执行时间
* (修改的是具体的trigger,不是jobdetail)
* @param name
* @param group
* @param time
* @return
* @throws SchedulerException
*/
public boolean modifyJob(String name, String group, String time) throws SchedulerException {
Date date = null;
TriggerKey triggerKey = new TriggerKey(name, group);
CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
String oldTime = cronTrigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(time)) {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(time);
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group)
.withSchedule(cronScheduleBuilder).build();
date = scheduler.rescheduleJob(triggerKey, trigger);
}
return date != null;
}
/**
* 暂停所有任务
*
* @throws SchedulerException
*/
public void pauseAllJob() throws SchedulerException {
scheduler.pauseAll();
}
/**
* 暂停某个任务
*
* @param name
* @param group
* @throws SchedulerException
*/
public void pauseJob(String name, String group) throws SchedulerException {
JobKey jobKey = new JobKey(name, group);
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if (jobDetail == null){
return;
}
scheduler.pauseJob(jobKey);
}
/**
* 恢复所有任务
*
* @throws SchedulerException
*/
public void resumeAllJob() throws SchedulerException {
scheduler.resumeAll();
}
/**
* 恢复某个任务
*
* @param name
* @param group
* @throws SchedulerException
*/
public void resumeJob(String name, String group) throws SchedulerException {
JobKey jobKey = new JobKey(name, group);
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if (jobDetail == null){
return;
}
scheduler.resumeJob(jobKey);
}
/**
* 删除某个任务
*
* @param name
* @param group
* @throws SchedulerException
*/
public void deleteJob(String name, String group) throws SchedulerException {
JobKey jobKey = new JobKey(name, group);
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if (jobDetail == null){
return;
}
scheduler.deleteJob(jobKey);
}
}
2.NeTaskJob.java:任务类,这里的关键是@PostConstruct,这是我们在实际项目中要调用其他接口的时候要用到的。
ZcTaskJob.java就是另一个任务,格式都一样,就里调用的接口不一样,这里就不写了。
import javax.annotation.PostConstruct;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class NeTaskJob implements Job{
@Autowired
private NeThreadService neThreadService;
public static NeTaskJob neTaskJob;
public NeTaskJob(){}
//注入要调用的方法
@PostConstruct
public void init(){
neTaskJob = this;
neTaskJob.neThreadService = this.neThreadService;
}
// private void before(){
// System.out.println("任务开始执行");
// }
/* (non-Javadoc)
* @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
*/
@Override
public void execute(JobExecutionContext JobExecutionContext) throws JobExecutionException {
// TODO Auto-generated method stub
// before();
System.out.println("任务开始:"+System.currentTimeMillis());
// TODO 业务:此处是关键,如何从map中取得对应的job名称(netask的唯一标识id)
NeTask netask = (NeTask)JobExecutionContext.getMergedJobDataMap().get("id");
//实际项目中经常要调用到其他的接口方法,那么一定要在上方注入PostConstruct
neTaskJob.neThreadService.sendCmd(netask.getPeriodValue());
System.out.println("任务结束:"+ netask.getTitle() +System.currentTimeMillis());
// after();
}
// private void after(){
// System.out.println("任务开始执行");
// }
}
3.service就是大家自己从数据库取任务,更新任务的方法,就不写了。
4.listener
@Component
public class AppListener implements ApplicationListener{
private static boolean loaded = false;
@Autowired
private QuartzScheduler quartzScheduler;
@Override
public void onApplicationEvent(ApplicationEvent e){
if(e instanceof ContextRefreshedEvent){
if(!loaded){//避免多次执行
loaded = true;
//定时任务启动
try {
quartzScheduler.startJob();
System.out.println("任务已经启动...");
} catch (SchedulerException se) {
se.printStackTrace();
}
}
if(e instanceof ContextStartedEvent){
}
}
}
/**
* 初始注入scheduler
* @return
* @throws SchedulerException
*/
@Bean
public Scheduler scheduler() throws SchedulerException{
SchedulerFactory schedulerFactoryBean = new StdSchedulerFactory();
return schedulerFactoryBean.getScheduler();
}
}
三、中途会遇到的一些问题
1.The job (0000000035.2) referenced by the trigger does not exist.
2.Jobs added with no trigger must be durable.
这些问题都是job配置多个触发器时需要注意的问题:
第一次scheduler2.scheduleJob(jobDetail, cronTrigger);必须有jobDetail,第二个trigger不能有jobDetail,scheduler2.scheduleJob(cronTrigger);,同时注意在newTrigger的时候必须.forJob(jobDetail)指明触发器是给哪个job用的,范例见QuartzScheduler.java;
此致,关于配置的问题就说这么多。关于实际应用中集成cron的问题,我们下篇再说。
在做的过程中筛选出来的几个对我参考价值较高的链接如下:
quartz:
https://www.cnblogs.com/qlqwjy/p/8721982.html
https://blog.csdn.net/weixin_40692498/article/details/88025275
https://blog.csdn.net/upxiaofeng/article/details/79415108#commentBox
https://blog.csdn.net/qq_28483283/article/details/80623417#commentBox
https://blog.csdn.net/u013042707/article/details/82934725#commentBox
https://blog.csdn.net/liuchuanhong1/article/details/60873295
https://blog.csdn.net/qq_29145405/article/details/81843123
cron:
https://www.cnblogs.com/a8457013/p/8515939.html