spring源码深入理解 (三): 定时任务框架各种使用方式

本篇主要介绍Spring 定时任务框架 的几种使用方式,以及动态定时任务

简单使用方式

  • 首先开启注解
@SpringBootApplication
@EnableScheduling
public class Springbootdemo1Application {
	public static void main(String[] args) {
		SpringApplication.run(Springbootdemo1Application.class, args);
	}
}
  • 其次方法上使用定时任务注解为@Scheduled(*)
@Component
public class ScheduledTasks {

	private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);

	private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

	@Scheduled(fixedRate = 5000)
	public void reportFixedRate() {
		log.info("reportFixedRate The time is now {}", dateFormat.format(new Date()));
	}

	@Scheduled(cron = "0/15 * * * * ?")
	public void reportCron() {
		log.info(" reportCron The  time is now {}", dateFormat.format(new Date()));
	}

}

支持设置的调度规则
内部可以设置的调度规则有 cron zone fixedDelay fixedDelayString fixedRate
initialDelay initialDelayString

依据测试打印的日志 fixedRate是循环每隔5秒执行任务,cron表达式语法语法 支持的是每隔15秒进行打印日志 ,上面是简单的分析打印的日志,使用最简单的方式实现定时任务,开发相当的轻松;

cron表达式详解语法(注:[年]不是必须的域,可以省略[年],则一共6个域 )
[秒] [分] [小时] [日] [月] [周] [年]
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期六凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
cron表达式使用占位符
cron属性接收的cron表达式支持占位符。"${time.cron}"

* 表示所有值。 例如:在分的字段上设置 *,表示每一分钟都会触发。
? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为”?” 具体设置为 0 0 0 10 * ?
- 表示区间。例如 在小时上设置 “10-12”,表示 10,11,12点都会触发。
, 表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发
/ 用于递增触发。如在秒上面设置”5/15” 表示从5秒开始,每增15秒触发(5,20,35,50)。 在日字段上设置’1/3’所示每月1号开始,每隔三天触发一次 */y,它等同于 0/y。
L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于”7”或”SAT”。如果在”L”前加上数字,则表示该数据的最后一个。例如在周字段上设置”6L”这样的格式,则表示“本月最后一个星期五”
W (work)表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上置”15W”,表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,”W”前只能设置具体的数字,不允许区间”-“)。
# 序号(表示每月的第几个周几),例如在周字段上设置”6#3”表示在每月的第三个周六.注意如果指定”#5”,正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了) ;小提示:’L’和 ‘W’可以一组合使用。如果在日字段上设置”LW”,则表示在本月的最后一个工作日触发;周字段的设置,若使用英文字母是不区分大小写的,即MON与mon相同。

fixedDelay

上一次执行完毕时间点之后多长时间再执行 以秒为单位

fixedDelayString

fixedDelay 意思相同,只是使用字符串的形式。唯一不同的是支持占位符

fixedRate

上一次开始执行时间点之后多长时间再执行

fixedRateString

第一次延迟多长时间后再执行

下面进行扩展出spring提供的方式,包括自定义动态任务

1.在方法上使用定时任务注解为@Scheduled(*)

2.Xml配置方式

<task:scheduled ref="taskJob" method="job1" cron="0 0 5 * * ?"/> </task:scheduled-tasks> 

3.实现SchedulingConfigurer接口,并持有ScheduledTaskRegistrar进行动态注册任务
下面方法包括整个自定义动态注册任务的流程

  	@Override
	public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
		this.registrar = scheduledTaskRegistrar;
		// 自定义任务调度器
		TaskScheduler taskScheduler = getTaskScheduler();
		scheduledTaskRegistrar.setTaskScheduler(taskScheduler);
		try {
			// 解析缓存
			CacheUtils.parseProcessConf(xmlPath);
		} catch (Exception e) {
			LOGGER.error("parseProcessConf Exception : {}", e.getMessage(), e);
		}
		// 遍历所有的任务进行执行
		CacheUtils.SCHEDULED_MAP.keySet().forEach(key -> {
			Scheduled scheduled = CacheUtils.SCHEDULED_MAP.get(key);
			if (scheduled != null && scheduled.isScheduledTask()) {// 判断是否有任务
				List<TaskDomain> listTask = scheduled.getTaskList();// 并拿到对应的任务
				if (listTask != null && listTask.size() > 0) {
					try {
						Method method = scheduled.getTargetClass().getDeclaredMethod(scheduled.getMethod(), key,
								String.class);
						listTask.stream().forEach(task -> {// 添加任务进去
							processScheduled(scheduled, method, task, scheduled.getTargetClass());
						});
					} catch (Exception e) {
						e.printStackTrace();
					}

				}
			}
		});
	}

我把任务调度的核心线程数设置为10 ,表示同一时间可以同时运行10个任务

	/*
	 * 自定义任务调度器
	 */
	@Bean(destroyMethod = "shutdown")
	public ThreadPoolTaskScheduler getTaskScheduler() {
		ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
		scheduler.setPoolSize(10);
		scheduler.setWaitForTasksToCompleteOnShutdown(true);
		return scheduler;
	}

自己定义了一个 ruannable任务执行器

/**
	 * 创建 任务 runnable
	 * 
	 * @param target
	 * @param method
	 * @return
	 */
	protected Runnable createRunnable(Object target, Method method, Object task) {
		Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());
		return new ScheduledMethodRunnable(target, invocableMethod, task);
	}

解析任务并对添加入注册器中

/**
	 * 解析任务并对添加入注册器中
	 * 
	 * @param scheduled
	 * @param method
	 * @param taskList
	 * @param bean
	 */
	protected void processScheduled(Scheduled scheduled, Method method, Object task, Object bean) {
		try {
			Runnable runnable = createRunnable(bean, method, task);
			boolean processedSchedule = false;
			String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";

			Set<ScheduledTask> tasks = new LinkedHashSet<>(4);

			// Determine initial delay
			long initialDelay = -1;
			String initialDelayString = scheduled.getInitialDelayString();
			if (StringUtils.hasText(initialDelayString)) {
				Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
				if (this.embeddedValueResolver != null) {
					initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
				}
				if (StringUtils.hasLength(initialDelayString)) {
					try {
						initialDelay = parseDelayAsLong(initialDelayString);
					} catch (RuntimeException ex) {
						throw new IllegalArgumentException("Invalid initialDelayString value \"" + initialDelayString
								+ "\" - cannot parse into long");
					}
				}
			}

			String cron = scheduled.getCron();
			if (StringUtils.hasText(cron)) {
				String zone = scheduled.getZone();
				if (this.embeddedValueResolver != null) {
					cron = this.embeddedValueResolver.resolveStringValue(cron);
					zone = this.embeddedValueResolver.resolveStringValue(zone);
				}
				if (StringUtils.hasLength(cron)) {
					Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
					processedSchedule = true;
					if (!cron.equals(scheduled.getCron_disabled())) {
						TimeZone timeZone;
						if (StringUtils.hasText(zone)) {
							timeZone = StringUtils.parseTimeZoneString(zone);
						} else {
							timeZone = TimeZone.getDefault();
						}
						tasks.add(this.registrar
								.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
					}
				}
			}

			// At this point we don't need to differentiate between initial delay set or not
			// anymore
			if (initialDelay < 0) {
				initialDelay = 0;
			}

			// Check fixed delay
			long fixedDelay = -1;
			if (fixedDelay >= 0) {
				Assert.isTrue(!processedSchedule, errorMessage);
				processedSchedule = true;
				tasks.add(
						this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
			}
			String fixedDelayString = scheduled.getFixedDelayString();
			if (StringUtils.hasText(fixedDelayString)) {
				if (this.embeddedValueResolver != null) {
					fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
				}
				if (StringUtils.hasLength(fixedDelayString)) {
					Assert.isTrue(!processedSchedule, errorMessage);
					processedSchedule = true;
					try {
						fixedDelay = parseDelayAsLong(fixedDelayString);
					} catch (RuntimeException ex) {
						throw new IllegalArgumentException(
								"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
					}
					tasks.add(this.registrar
							.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
				}
			}

			// 固定时间
			long fixedRate = -1;
			if (fixedRate >= 0) {
				Assert.isTrue(!processedSchedule, errorMessage);
				processedSchedule = true;
				tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
			}
			String fixedRateString = scheduled.getFixedRateString();
			if (StringUtils.hasText(fixedRateString)) {
				if (this.embeddedValueResolver != null) {
					fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
				}
				if (StringUtils.hasLength(fixedRateString)) {
					Assert.isTrue(!processedSchedule, errorMessage);
					processedSchedule = true;
					try {
						fixedRate = parseDelayAsLong(fixedRateString);
					} catch (RuntimeException ex) {
						throw new IllegalArgumentException(
								"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
					}
					tasks.add(
							this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
				}
			}

			// 最后注册计划任务 同一个bean默认新建任务初始化大小为16
			synchronized (this.scheduledTasks) {
				Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
				regTasks.addAll(tasks);
			}
		} catch (IllegalArgumentException ex) {
			throw new IllegalStateException(
					"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
		}
	}

实现DisposableBean接口,管理 任务销毁

/**
	 * 容器销毁该bean时会调用该方法进行销毁
	 */
	@Override
	public void destroy() {
		synchronized (this.scheduledTasks) {
			Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
			for (Set<ScheduledTask> tasks : allTasks) {
				for (ScheduledTask task : tasks) {
					task.cancel();
				}
			}
			this.scheduledTasks.clear();
		}
	}

解析自己的任务,我是自定义xml的进行解析

public class CacheUtils {

	private final static Logger LOGGER = LoggerFactory.getLogger(ScheduleTaskConfigurer.class);

	public static final Map<Class<?>, Scheduled> SCHEDULED_MAP = new HashMap<>();

	// 遍历xml文件
	public static void parseProcessConf(String xmlPath) throws Exception {
		LOGGER.info("parseProcessConf xmlPath : {} ", xmlPath);
		// 获取xml文件列表
		File xmlFile = new File(xmlPath);
		//
		SAXReader reader = new SAXReader();
		Document document = reader.read(xmlFile);
		Element root = document.getRootElement();

		// 解析task任务列表
		Element task = root.element("task");
		if (task != null) {
			List<TaskDomain> taskList = parseXml(task);
			if (taskList == null || taskList.size() == 0) {
				LOGGER.error(" taskList is null");
			} else {
				putSheduled(task, taskList);
			}
		}
	}

	private static void parseXml(File xmlFile) throws Exception {

	}

	private static List<TaskDomain> parseXml(Element task) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * 解析任务执行规则
	 * 
	 * @param key     任务类型
	 * @param element 解析节点
	 */
	public static void putSheduled(Element element, List<TaskDomain> taskList) {
		Scheduled scheduled = new Scheduled();
		scheduled.setCron(element.attributeValue("cron"));
		scheduled.setFixedDelayString(element.attributeValue("fixedDelayString"));
		scheduled.setFixedRateString(element.attributeValue("fixedRateString"));
		scheduled.setMethod(element.attributeValue("method"));
//		scheduled.setTargetClass(element.attributeValue("targetClass"));
		scheduled.setTaskList(taskList);
		CacheUtils.SCHEDULED_MAP.put(taskList.get(0).getClass(), scheduled);
	}
}

包括下面的这是spring提供的一些例子供我们参考使用的

/**
*启用Spring的计划任务执行功能,类似于
*Spring的{@code<task:*>}XML命名空间中的功能。待使用
*在@{@link Configuration}类上,如下所示:
*<pre class=“code”>
* @Configuration
* @EnableScheduling
*公共类AppConfig{
*// various @;Bean definitions
*}</pre>
*这允许在任何Spring管理的数据库上检测@{@link Scheduled}注释
*容器中的注解。例如,给定一个类{@code MyTask}
* package com.myco.tasks;
*public class MyTask {
* @Scheduled(fixedRate=1000)
*public void work() {
*//任务执行逻辑
* }
*}</pre>
*以下配置将确保调用{@code MyTask.work()}
*每1000毫秒一次
* @Configuration
 * @EnableScheduling
 * public class AppConfig {
 *    @Bean
 *     public MyTask task() {
 *         return new MyTask();
 *     }
 * }</pre>
*或者,如果用{@code@Component}注释了{@code@MyTask},则
*以下配置将确保其{@code@Scheduled}方法
*按所需间隔调用:
*<pre class=“code”>
* @Configuration
 * @ComponentScan(basePackages="com.myco.tasks")
 * public class AppConfig {
 * }</pre>
*用{@code@Scheduled}注释的方法甚至可以直接在
*{@code@Configuration}类:
 * <pre class="code">
 * @Configuration
 * @EnableScheduling
 * public class AppConfig {
 *     @Scheduled(fixedRate=1000)
 *     public void work() {
 *         // task execution logic
 *     }
 * }</pre>
*在上述所有场景中,都使用默认的单线程任务执行器。
*当需要更多的控制时,{@code@Configuration}类可以实现
*{@link SchedulingConfigurer}。这允许访问底层
*{@link scheduledtaskregistar}实例。例如,以下示例
*演示如何自定义用于执行调度的{@link Executor}
*任务:
*<pre class=“code”>
 * @Configuration
 * @EnableScheduling
 * public class AppConfig implements SchedulingConfigurer {
 *     @Override
 *     public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
 *         taskRegistrar.setScheduler(taskExecutor());
 *     }
 *
 *     @Bean(destroyMethod="shutdown")
 *     public Executor taskExecutor() {
 *         return Executors.newScheduledThreadPool(100);
 *     }
 * }</pre>
*
*注意上面的例子中{@code@Bean(destroyMethod=“shutdown”)}的用法。这个
*确保任务执行器在Spring应用程序启动时正确关闭
*上下文本身是封闭的。
*实现{@code SchedulingConfigurer}还允许细粒度的
*通过{@code scheduledtaskregistar}控制任务注册。
*例如,下面配置特定bean的执行
*每个自定义{@code Trigger}实现的方法:
*<pre class=“code”>
  @Configuration
 *@EnableScheduling
 * public class AppConfig implements SchedulingConfigurer {
 *     @Override
 *     public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
 *         taskRegistrar.setScheduler(taskScheduler());
 *         taskRegistrar.addTriggerTask(
 *             new Runnable() {
 *                 public void run() {
 *                     myTask().work();
 *                 }
 *             },
 *             new CustomTrigger()
 *         );
 *     }
 *    @Bean(destroyMethod="shutdown")
 *     public Executor taskScheduler() {
 *         return Executors.newScheduledThreadPool(42);
 *     }
 *     @Bean
 *     public MyTask myTask() {
 *         return new MyTask();
 *     }
 * }</pre>
*作为参考,上面的示例可以与下面的Spring XML进行比较
* 配置:
*<pre class=“code”>
 * {@code
 * <beans>
 *     <task:annotation-driven scheduler="taskScheduler"/>
 *     <task:scheduler id="taskScheduler" pool-size="42"/>
 *     <task:scheduled ref="myTask" method="work" fixed-rate="1000"/>
 *     <bean id="myTask" class="com.foo.MyTask"/>
 * </beans>
 * }</pre>
*除了在XML中使用固定利率时段外,这些示例是等效的
*而不是定制的<em>{@code Trigger}</em>实现;这是因为
*{@code task:}命名空间{@code scheduled}无法轻松公开此类支持。这是
*但是有一个例子说明了基于代码的方法如何允许最大的可配置性
*通过直接访问实际组件。<p>
 * @author Chris Beams
 * @since 3.1
 * @see Scheduled
 * @see SchedulingConfiguration
 * @see SchedulingConfigurer
 * @see ScheduledTaskRegistrar
 * @see Trigger
 * @see ScheduledAnnotationBeanPostProcessor
 */

总结

spring定时任务器是sping-context中提供,所提供的三种方式也相当强大,但是不支持分布式定时任务,这个就需要用到xx-job这些框架,这里就不在这里叙述; 如需要自定义注解方式实现,需要看我下一篇文章,spring定时任务源码解析

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

踩踩踩从踩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值