需求:用户可以设定在未来某天发送一个邮件(发邮件还是干什么视读者自身情况而定)。
难点:
- 让用户们追加的任务能够及时地刷新在待执行列表中。(排序、另开任务固定频率读取数据库的任务信息)
- 时间点A被注册的任务如何在时间点B(也许是用户主动取消,也许是固定逻辑需要)还能被关闭。(存放已被注册的ScheduledFuture对象以进行控制)。
- 如果下一个将要执行今晚八点的任务,而突然有个用户追加了一个七点半要执行的任务该如何进行插队,总不能因为马上要执行八点的就把现在到七点五十九的任务无视。
环境:IDEA+SpringBoot(springboot自带定时任务)+MyBatis(MySql)
一、启用定时任务
1 编写一个类来存放我们的定时任务相关代码。
并在其上增加开启计划任务注解以及开启异步注解
@Component
@EnableScheduling
@EnableAsync
2 在其中注入一个我们需要的线程池,以防springboot采用自己默认的单线程处理。原因在于@EnableScheduling->SchedulingConfiguration->ScheduledAnnotationBeanPostProcessor->ScheduledTaskRegistrar->scheduleTasks()方法有如下判断:
protected void scheduleTasks() {
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
......
}
所以我们在自己的测试类中用@Bean注册一个线程池:
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
return new ThreadPoolTaskScheduler();
}
3 接下来我们写一个void方法,在其上增加@Scheduled
注解来表明这是一个计划,并追加@Async
来开启异步。
@Async
@Scheduled(cron = "0/5 * * * * *")
二、逻辑理解
此刻我想要每隔五秒读取所有任务信息,并将读取到的任务注册,执行后的任务需要关闭掉,所以需要以下代码(最后会粘出完整代码)
1 每隔一段时间查询一次数据,为了测试方便设定五秒。那么就需要写一个定时任务,每隔五秒查询数据,并且这个任务需要保持。
@Async
@Scheduled(cron = "0/5 * * * * *")
public void first() throws InterruptedException {
log.info("调度前list的size是:"+this.tasks.size());
//调用数据库进行查询
this.tasks = taskService.getMailTasks();
log.info("调度后list的size是:"+this.tasks.size());
}
运行如下:
可以看到相差五秒,这样就能以固定间隔获取任务信息了。
2 任务信息里面有根据用户设置时间而解析出的cron表达式,那么查询后有了对象集合就需要将他们封装成任务。
a.想要把读取到的信息转化为计划任务,就需要依赖线程池的threadPoolTaskScheduler.schedule(runnable,cronTrigger)
方法,我们把其他任务信息在runnable中用来控制处理逻辑,把解析的cron表达式传进第二个参数对象中设定时间。
b.由于a提到的方法会返回一个ScheduledFuture
对象,并且关闭任务也需要这个对象来执行cancle()
方法,所以我们用一个ConcurrentHashMap
来存放,以任务的ID为键,以这个ScheduledFuture
对象为值。
完整代码如下,被注调的mailService是我发邮件的处理,各位自行改变:
c.当一个任务结束时,我们遍历这个map,找到对应的任务,调用值里ScheduledFuture
对象的cancle(true)
方法关闭任务,并从这个Map中remove
调对象
for(MailModel mail:tasks){
log.info("现在是任务:"+mail.getTid());
futures.put(mail.getTid().toString(),threadPoolTaskScheduler.schedule(new Runnable() {
@Override
public void run() {
// mailService.sendSimpleMail(mail.getToUser(),mail.getMailTitle(),mail.getMailContents());
System.out.println("任务执行!变量设置为1");
System.out.println(mail.getMailContents());
System.out.println(mail.toString());
System.out.println("遍历map");
for(Map.Entry<String,ScheduledFuture<?>> scheduledFutureEntry:futures.entrySet()){
if(scheduledFutureEntry.getKey().equals(mail.getTid().toString())&&mail.getTid()==1){
System.out.println("找到了匹配的这个任务,执行关闭,并在map中清除");
scheduledFutureEntry.getValue().cancel(true);
futures.remove(scheduledFutureEntry.getKey());
System.out.println("清除后任务容量:"+futures.size());
}
}
}
}, new CronTrigger(mail.getTchron())));
运行结果:可以看到我的两个任务的定时,一个是每隔五秒打印,一个是每隔10秒打印,现在根据我逻辑中这段代码:
if(scheduledFutureEntry.getKey().equals(mail.getTid().toString())&&mail.getTid()==1)
这个五秒一执行且id为1的任务将在第一次执行后就被关闭且移除,而十秒一执行的任务将会一直存在。我们看看运行结果:
首先,定时查询任务启动,将数据库两个任务信息读出并执行,显示map中两个任务:
然后,五秒一执行的任务先触发,并在执行后将自己结束并移除,容量剩一个。
最后,十秒一执行的任务由于不满足id==1,所以一直按照预定的计划运作了起来。
小结:到这里大致思路就结束了,我们可以从库里动态获取任务,也可以根据任务的唯一标识存/取他的future对象,那么剩下的何时关,何时开,何时读,何时写,何时操作map,相关的对象都已经在手,剩下的根据自己的功能去实现即可。
如有不对请务必为我指正,如有帮助记得点个赞呀。