spring源码深入理解 (二):定时任务管理-源码核心解析

这篇文章主要解析spring定时任务的源码,如果需要看使用方式请看我上篇文章

前言

该篇文章介绍Scheduled注解到怎么扫描并注册任务,执行定时任务,各个类的源码解析

定时任务管理各个功能解析

核心类摘要

  • ScheduledAnnotationBeanPostProcessor(任务注解解析器)

  • ScheduledTaskRegistrar(任务注册器)

  • TaskScheduler (任务调度器 默认ThreadPoolTaskScheduler )

  • ReschedulingRunnable(可重复调度的可运行任务)

    中间涉及到的比较重要的类

  • CronTask ( 封装的定时任务类)

  • ScheduledThreadPoolExecutor (jdk提供的定时定时线程池类)

  • SchedulingConfiguration 接口

源码是在 org.springframework.scheduling包下面的

  • 注册任务解析器
    首先 查看@EnableScheduling 注解
    具体的import
    import详解

EnableScheduling注释

/**
*启用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
 */

并直接在写了EnableScheduling 的demo
介绍任务的Spring的计划任务执行功能
在这里插入图片描述

任务注解解析器(ScheduledAnnotationBeanPostProcessor)

全篇注释

/**
*注册用@{@link Scheduled}注释的方法的Bean后处理器
*由{@link org.springframework.scheduling.TaskScheduler}根据
*通过注释提供的“fixedRate”、“fixedDelay”或“cron”表达式。
*<p>这个后处理器由Spring的
*{@代码<task:annotation-driven>}XML元素,以及
*@{@link EnableScheduling}注释。
*<p>自动检测容器中的任何{@link SchedulingConfigurer}实例,
*允许定制要使用的调度程序或进行细粒度的
*对任务注册的控制(例如{@link Trigger}任务的注册)。
*有关完整的用法详细信息,请参阅@{@link EnableScheduling}javadocs。
 - @author Mark Fisher
 - @author Juergen Hoeller
 - @author Chris Beams
 - @since 3.0
 - @see Scheduled
 - @see EnableScheduling
 - @see SchedulingConfigurer
 - @see org.springframework.scheduling.TaskScheduler
 - @see org.springframework.scheduling.config.ScheduledTaskRegistrar
 */
  • 管理任务注册器

自动创建 ScheduledTaskRegistrar,并持有

	/**
	 * Create a default {@code ScheduledAnnotationBeanPostProcessor}.
	 */
	public ScheduledAnnotationBeanPostProcessor() {
		this.registrar = new ScheduledTaskRegistrar();
	}

  • 管理所有任务:bean销毁时,任务自动销毁。

利用DisposableBean 接口,交给ioc框架 自动销毁任务

	@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();
		}
		this.registrar.destroy();
	}
  • 解析@Scheduled注解,封装成任务添加到任务注册器中。

    实现 beanpostprocesse接口,容器所有bean实例化之后会调用该postProcessAfterInitialization 方法,然后 解析注解并封装成任务(例如 CronTask ),并将任务注册ScheduledTaskRegistrar
    任务执行顺序 在任务注册器中执行顺序,先执行cron,之后再执行fixedRate

beanpostprocess实现原理

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
				bean instanceof ScheduledExecutorService) {
			// Ignore AOP infrastructure such as scoped proxies.
			return bean;
		}

		Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
		if (!this.nonAnnotatedClasses.contains(targetClass) &&
				AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
			Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
					(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
						Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
								method, Scheduled.class, Schedules.class);
						return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);
					});
			if (annotatedMethods.isEmpty()) {
				this.nonAnnotatedClasses.add(targetClass);
				if (logger.isTraceEnabled()) {
					logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
				}
			}
			else {
				// Non-empty set of methods
				annotatedMethods.forEach((method, scheduledAnnotations) ->
						scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
				if (logger.isTraceEnabled()) {
					logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
							"': " + annotatedMethods);
				}
			}
		}
		return bean;
	}
  • 方法调用栈

在这里插入图片描述

  • 动态生成定时任务调度器

ScheduledAnnotationBeanPostProcessor实现了ApplicationListener接口 (该接口的作用,当Spring容器将初始化完成之后 调用该方法) 调用 onApplicationEvent () 方法

applicationevent原理

  • 加载 实现SchedulingConfigurer 接口的类
	if (this.beanFactory instanceof ListableBeanFactory) {
			Map<String, SchedulingConfigurer> beans =
					((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
			List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());
			AnnotationAwareOrderComparator.sort(configurers);
			for (SchedulingConfigurer configurer : configurers) {
				configurer.configureTasks(this.registrar);
			}
		}

  • 创建 TaskScheduler (任务调度器 默认ThreadPoolTaskScheduler)

通过NamedBeanHolder动态生成 (这里可以设置执行线程大小等)

try {
				// Search for TaskScheduler bean...
				this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
			}
  • 最后调用任务注册器的方法,执行任务 this.registrar.afterPropertiesSet();
this.registrar.afterPropertiesSet();
  • 方法调用栈

在这里插入图片描述

  • 其他一些小功能

存储没有注解的类,降低重复扫描

	private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));

提供直接从application 中读取数据, 而不用 @Value
使用EmbeddedValueResolverAware解析配置文件

在这里插入图片描述

定时任务注册器(ScheduledTaskRegistrar)

  • ScheduledTaskRegistrar全篇注释
/**
*用于向{@link TaskScheduler}注册任务的Helper bean,通常使用cron
*表达式。
*<p>从spring3.1开始,{@code scheduledtaskregistar}有一个更突出的面向用户的
*与@{@链接一起使用时的角色
*org.springframework.scheduling.annotation.EnableAsync EnableAsync}注释及其应用
*{@link org.springframework.scheduling.annotation.SchedulingConfigurer
*SchedulingConfigurer}回调接口。
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 3.0
 * @see org.springframework.scheduling.annotation.EnableAsync
 * @see org.springframework.scheduling.annotation.SchedulingConfigurer
 */
  • 管理所有的任务 :任务注册方法;bean销毁时,任务自动销毁。 包括各个任务的注册

在这里插入图片描述

  • 也是实现了DisposableBean接口 实现任务的清除

disposablebean原理

@Override
	public void destroy() {
		for (ScheduledTask task : this.scheduledTasks) {
			task.cancel();
		}
		if (this.localExecutor != null) {
			this.localExecutor.shutdownNow();
		}
	}

  • 管理任务调度器 :管理调度器并执行任务,设置调度器 。

当调度器为空的情况会自动创建一个单线程的线程池
(newSingleThreadScheduledExecutor只会有一个线程,不管你提交多少任务,这些任务会顺序执行,如果发生异常会取消下面的任务,线程池也不会关闭)

	if (this.taskScheduler == null) {
			this.localExecutor = Executors.newSingleThreadScheduledExecutor();
			this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
		}
  • 调用方法栈

在这里插入图片描述

任务调度器(TaskScheduler)

  • 注释
/**
*Spring的{@link TaskScheduler}接口的实现,包装
*本地{@link java.util.concurrent.ScheduledThreadPoolExecutor}。
 * @author Juergen Hoeller
 * @author Mark Fisher
 * @since 3.0
 * @see #setPoolSize
 * @see #setRemoveOnCancelPolicy
 * @see #setThreadFactory
 * @see #setErrorHandler
 */
  • 管理定时任务类 ,初始化设置 线程数大小等

初始化线程池管理定时类 (默认 ScheduledThreadPoolExecutor ,并且单线程)

在这里插入图片描述
调用方法栈
在这里插入图片描述
通过 设置setPoolSize方法 设置核心线程数

	public void setPoolSize(int poolSize) {
		Assert.isTrue(poolSize > 0, "'poolSize' must be 1 or higher");
		this.poolSize = poolSize;
		if (this.scheduledExecutor instanceof ScheduledThreadPoolExecutor) {
			((ScheduledThreadPoolExecutor) this.scheduledExecutor).setCorePoolSize(poolSize);
		}
	}

  • 执行具体的定时任务 创建 可重复调度的运行任务类

最终将task和trigger都封装到了ReschedulingRunnable中 定时任务。

	@Override
	@Nullable
	public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
		ScheduledExecutorService executor = getScheduledExecutor();
		try {
			ErrorHandler errorHandler = this.errorHandler;
			if (errorHandler == null) {
				errorHandler = TaskUtils.getDefaultErrorHandler(true);
			}
			return new ReschedulingRunnable(task, trigger, this.clock, executor, errorHandler).schedule();
		}
		catch (RejectedExecutionException ex) {
			throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
		}
	}
  • 延时任务,直接执行executor.scheduleAtFixedRate。
	@Override
	public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
		ScheduledExecutorService executor = getScheduledExecutor();
		long initialDelay = startTime.getTime() - this.clock.millis();
		try {
			return executor.scheduleAtFixedRate(errorHandlingTask(task, true), initialDelay, period, TimeUnit.MILLISECONDS);
		}
		catch (RejectedExecutionException ex) {
			throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
		}
	}

  • 其他小功能

包括提供活跃数获取等
在这里插入图片描述

由返回值的线程
在这里插入图片描述

  • 可重复调度的运行任务类(ReschedulingRunnable),主要包含的是下面功能。

ReschedulingRunnable schedule方法加了同步锁,只能有一个线程拿到下次执行时间并加入执行器的调度。

/**
*内部适配器,根据
*到给定的{@link Trigger}建议的下一个执行时间。
*<p>因为本机{@link ScheduledExecutorService}支持
*仅限延迟驱动执行。{@link Trigger}接口的灵活性
*将转换为下一次执行时间的延迟(重复)。
 * @author Juergen Hoeller
 * @author Mark Fisher
 * @since 3.0
 */

不同的ReschedulingRunnable对象之间在线程池够用的情况下是不会相互影响的,也就是说满足线程池的条件下,TaskScheduler的schedule方法的多次调用是可以交叉执行的。

	@Nullable
	public ScheduledFuture<?> schedule() {
		synchronized (this.triggerContextMonitor) {
			this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
			if (this.scheduledExecutionTime == null) {
				return null;
			}
			long initialDelay = this.scheduledExecutionTime.getTime() - this.triggerContext.getClock().millis();
			this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
			return this;
		}
	}
  • 执行具体的定时任务

(java.util.concurrent .ScheduledThreadPoolExecutor中重复进行调用)
下面这部分是jdk中的线程池执行定时任务的类

在这里插入图片描述

ScheduledFutureTask 工作原理如下图所示

在这里插入图片描述
1、ScheduledFutureTask会放入优先阻塞队列:ScheduledThreadPoolExecutor.DelayedWorkQueue(二叉最小堆实现)
2、上图中的Thread对象即ThreadPoolExecutor.Worker,实现了Runnable接口
3、 Worker中维护了Thread对象,Thread对象的Runnable实例即Worker自身
  2、ThreadPoolExecutor#addWorker方法中会创建Worker对象,然后拿到Worker中的thread实例并start,这样就创建了线程池中的一个线程实例
  3、Worker的run方法会调用ThreadPoolExecutor#runWorker方法,这才是任务最终被执行的地方,该方法示意如下
  (1)首先取传入的task执行,如果task是null,只要该线程池处于运行状态,就会通过getTask方法从workQueue中取任务。ThreadPoolExecutor的execute方法会在无法产生core线程的时候向  workQueue队列中offer任务。
getTask方法从队列中取task的时候会根据相关配置决定是否阻塞和阻塞多久。如果getTask方法结束,返回的是null,runWorker循环结束,执行processWorkerExit方法。
至此,该线程结束自己的使命,从线程池中“消失”。
  (2)在开始执行任务之前,会调用Worker的lock方法,目的是阻止task正在被执行的时候被interrupt,通过调用clearInterruptsForTaskRun方法来保证的(后面可以看一下这个方法),该线程没有自己的interrupt set了。
  (3)beforeExecute和afterExecute方法用于在执行任务前后执行一些自定义的操作,这两个方法是空的,留给继承类去填充功能。
我们可以在beforeExecute方法中抛出异常,这样task不会被执行,而且在跳出该循环的时候completedAbruptly的值是true,表示the worker died due to user exception,会用decrementWorkerCount调整wc。
  (4)因为Runnable的run方法不能抛出Throwables异常,所以这里重新包装异常然后抛出,抛出的异常会使当当前线程死掉,可以在afterExecute中对异常做一些处理。
  (5)afterExecute方法也可能抛出异常,也可能使当前线程死掉。

总结:spring 的定时任务并不难,仔细看你会发发现其实就是核心类,并且利用spring框架的特性 ,扩展性强;当你理解过后,我们可以自定义注解器,包括自定义执行线程数等等,多种方式。

注:import详解 :https://zhuanlan.zhihu.com/p/147025312
beanpostprocess实现原理 :https://www.cnblogs.com/youzhibing/p/10559330.html
applicationevent原理 :https://blog.csdn.net/liyantianmin/article/details/81017960
是做了个超链接,我自己还没来得及分析,先引用别人的解析。
以及ScheduledFutureTask 工作原理图画。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

踩踩踩从踩

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

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

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

打赏作者

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

抵扣说明:

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

余额充值