文档章节

spring boot 学习笔记(4) 动态创建定时任务

小镇刁民
 小镇刁民
发布于 2017/03/21 19:00
字数 992
阅读 1138
收藏 0

最近项目里面需要做动态定时任务的功能,需要查询数据库里面的某个表,然后动态的创建、修改、删除定时任务。作为一个伸手党,本来想着上网一查就能找到现成的方案,结果找了半天也没找到合适的,没办法自己动手研究一下吧。 还好在官方文档中翻到了一个SchedulingConfigurer类,查看源码发现configureTasks方法中,可以通过ScheduledTaskRegistrar.addCronTask实例来添加任务,嗯,看起来有戏,动手试一下。实现思路如下:

  • 通过@Scheduled注解创建一个定时任务,30秒执行一次,用于加载数据库中的定时任务列表
  • 继承SchedulingConfigurer类,通过ScheduledTaskRegistrar.addCronTask实例来添加任务
  • 创建DynamicTaskRunable 用来执行对应的任务操作
package com.spbd.task.job;

import java.util.List;

import javax.annotation.PreDestroy;

import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Component;

import com.spbd.task.model.TimingTask;
@Component
public class DynamicTaskConfigurer implements SchedulingConfigurer {
	
	private volatile ScheduledTaskRegistrar registrar;
	
	@Override
	public void configureTasks(ScheduledTaskRegistrar registrar) {
		this.registrar = registrar;
	}
	
	public void refreshTasks(List<TimingTask> tasks){
		for (TimingTask tt : tasks) {
			//判断是否已经存在该任务
			if(hasTask(tt.getTaskId())){
				DynamicTaskRunable t = new DynamicTaskRunable(tt.getTaskId());
				String expression = tt.getExpression();
				CronTask task = new CronTask(t, expression);
				registrar.addCronTask(task);
			}
		}
	}
	
	private boolean hasTask(Integer taskId){
		List<CronTask> taskList = registrar.getCronTaskList();
		for (CronTask cronTask : taskList) {
			if(cronTask.getRunnable() instanceof DynamicTaskRunable){
				DynamicTaskRunable r = (DynamicTaskRunable) cronTask.getRunnable();
				if(r.getTaskId().equals(taskId)){
					return true;
				}
			}
		}
		return false;
	}
	
	@PreDestroy
	public void destroy() {
		this.registrar.destroy();
	}
}

package com.spbd.task.job;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.spbd.task.model.TimingTask;

@Component
public class DynamicTask {
	
	private final static Logger LOGGER =Logger.getLogger(DynamicTaskRunable.class);
	@Autowired
	private DynamicTaskConfigurer dynamicTaskConfigurer;

	@Scheduled(cron="0/30 * * * * ?")
	public void loadTasks(){
		LOGGER.info("DynamicTask 正在执行......");
		//从数据库查询
		List<TimingTask> tasks = new ArrayList<TimingTask>();
		dynamicTaskConfigurer.refreshTasks(tasks);
	}
}

package com.spbd.task.job;

import org.apache.log4j.Logger;

public class DynamicTaskRunable implements Runnable{
	
	private final static Logger LOGGER =Logger.getLogger(DynamicTaskRunable.class);
	
	private Integer taskId;
	
	public DynamicTaskRunable(Integer taskId) {
		this.taskId = taskId;
	}
	@Override
	public void run() {
		LOGGER.info("DynamicTaskRunable is running, taskId:"+taskId);
	}

	public Integer getTaskId() {
		return taskId;
	}
}

当我几下把代码写出来之后,迫不及待的运行起来试了试,然而并没有什么用。。。

又看了一下源码才发现ScheduledTaskRegistrar.addCronTask只是把任务放到列表里面,只有在调用scheduleTasks方法的时候才会真正的创建定时任务,而这个方法只有在实例化之后才会去调用。于是脑袋上的小灯泡闪了一下,在refreshTasks方法中调用ScheduledTaskRegistrar.afterPropertiesSet()。于是我又迫不及待的试了试,诶,居然可以了。

毕竟我还是太年轻,正当我沾沾自喜的时候,发现定时任务的数量越来越多了,我勒个去,这不是我想要的。看样子一定是我打开的方式不对。继续看源码吧!

仔细查看源码发现ScheduledTaskRegistrar.scheduleTasks()方法每次都会遍历任务列表并且调用TaskScheduler.schedule()创建定时任务,所以才会创建很多的定时任务。

既然是通过TaskScheduler.schedule()创建定时任务的,那就在它上面想办法吧。改造之后的DynamicTaskConfigurer类如下:

package com.spbd.task.job;

import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

import javax.annotation.PreDestroy;

import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.spbd.task.model.TimingTask;
@Component
public class DynamicTaskConfigurer implements SchedulingConfigurer {
	
	private volatile ScheduledTaskRegistrar registrar;
	
	private final ConcurrentHashMap<Integer, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<Integer, ScheduledFuture<?>>();
	private final ConcurrentHashMap<Integer, CronTask> cronTasks = new ConcurrentHashMap<Integer, CronTask>();
	
	@Override
	public void configureTasks(ScheduledTaskRegistrar registrar) {
		this.registrar = registrar;
	}
	
	public void refreshTasks(List<TimingTask> tasks){
		//取消已经删除的策略任务
		Set<Integer> sids = scheduledFutures.keySet();
		for (Integer sid : sids) {
			if(!exists(tasks, sid)){				
				scheduledFutures.get(sid).cancel(false);
			}
		}
		for (TimingTask tt : tasks) {
			DynamicTaskRunable t = new DynamicTaskRunable(tt.getTaskId());
			String expression = tt.getExpression();
			if(StringUtils.isEmpty(expression)){
				continue;
			}
			if(scheduledFutures.containsKey(tt.getTaskId()) && cronTasks.get(tt.getTaskId()).getExpression().equals(expression)){
				continue;
			}
			//如果策略执行时间发生了变化,则取消当前策略的任务
			if(scheduledFutures.containsKey(tt.getTaskId())){
				scheduledFutures.get(tt.getTaskId()).cancel(false);
				scheduledFutures.remove(tt.getTaskId());
				cronTasks.remove(tt.getTaskId());
			}
			CronTask task = new CronTask(t, expression);
			ScheduledFuture<?> future = registrar.getScheduler().schedule(task.getRunnable(), task.getTrigger());
			cronTasks.put(tt.getTaskId(), task);
			scheduledFutures.put(tt.getTaskId(), future);
		}
	}
	
	private boolean exists(List<TimingTask> tasks,Integer tid){
		for(TimingTask task:tasks){
			if(task.getTaskId().equals(tid)){
				return true;
			}
		}
		return false;
	}
	
	@PreDestroy
	public void destroy() {
		this.registrar.destroy();
	}
}

再跑起来看了一下,暂时没有问题,后面再充分测试看看。。。

https://github.com/sergewu/spbd/tree/master/spbd-task

THE END

© 著作权归作者所有

共有 人打赏支持
小镇刁民
粉丝 3
博文 12
码字总数 7394
作品 0
成都
后端工程师
私信 提问
Spring Boot学习笔记

RabbitMQ RabbitMQ 安装 linux安装RabbitMQ详细教程 Ubuntu 16.04 RabbitMq 安装与运行(安装篇) ubantu安装rabbitMQ步骤 多线程 Spring @transactional注解和synchronized同步锁同时使用不...

OSC_fly
07/26
0
0
Spring Boot:在Spring Boot中使用定时任务

本文主要介绍如何在Spring Boot中使用定时任务,假设你已经建好了一个基础的Spring Boot项目。首先,我们在项目中建立一个定时任务。 1.创建定时任务 package hello;import java.text.Simpl...

Element0506
2015/11/10
0
0
Spring Boot点滴笔记

Spring Boot导入xml配置文件 使用Spring Boot后再也不想回到一堆xml配置的spring项目中了。但Spring boot有时候也避免不了需要xml配置文件。 可以在启动类中加入@ImportResource Spring Boot...

hutaishi
2017/11/01
0
0
新书上架:《Spring Boot 开发实战》(基于 Kotlin + Gradle + Spring Boot 2.0 的企业级服务端开发实战)

新书上架:《Spring Boot 开发实战》 — 基于 Kotlin + Gradle + Spring Boot 2.0 的企业级服务端开发实战 京东下单链接 https://item.jd.com/31178320122.html 天猫下单链接 https://detail...

程序员诗人
08/05
0
0
学习 Spring Boot 知识看这一篇就够了

从2016年因为工作原因开始研究 Spring Boot ,先后写了很多关于 Spring Boot 的文章,发表在技术社区、我的博客和我的公号内。粗略的统计了一下总共的文章加起来大概有六十多篇了,其中一部分...

ityouknow
05/28
0
0

没有更多内容

加载失败,请刷新页面

加载更多

当S8遇上边缘计算:谈阿里云ENS对直播业务场景的支撑

摘要: 类似S8赛事这样的大型活动直播的特点和技术挑战是什么?为什么业务要下沉到边缘?自建边缘节点和与云服务厂商合作到底该如何选择?边缘节点服务(ENS)又是如何进行技术支撑?提供的针...

阿里云官方博客
29分钟前
5
0
supervisor安装配置

supervisor安装配置 安装 wget -c https://files.pythonhosted.org/packages/44/60/698e54b4a4a9b956b2d709b4b7b676119c833d811d53ee2500f1b5e96dc3/supervisor-3.3.4.tar.gztar -zxvf su......

jackmanwu
39分钟前
1
0
laravel定时器

民间高手: https://blog.csdn.net/zhezhebie/article/details/79205414 官方文档: https://laravel-china.org/docs/laravel/5.5/scheduling/1325...

vio小黑
今天
1
0
Apache Zeppelin安装及使用

Apache Zeppelin官网:http://zeppelin.apache.org/ Apache Zeppelin介绍:A web-based notebook that enables interactive data analytics. You can make beautiful data-driven, interacti......

GordonNemo
今天
6
0
关于python开发多个项目环境配置Anaconda

关于Anaconda用来管理Python的包和环境 下载并安装Anaconda 创建项目:windows键+R ==> 进入CMD => 输入conda create -n 项目名称 python=3 conda info -e 查看项目以及项目所在的位置,默认...

上官清偌
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部