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

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

文章目录

    • 前言
    • 定时任务管理各个功能解析
    • EnableScheduling注释
    • 任务注解解析器(ScheduledAnnotationBeanPostProcessor)
    • 定时任务注册器(ScheduledTaskRegistrar)
    • 任务调度器(TaskScheduler)
    • ScheduledFutureTask 工作原理如下图所示

前言

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

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

核心类摘要

  • ScheduledAnnotationBeanPostProcessor(任务注解解析器)

  • ScheduledTaskRegistrar(任务注册器)

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

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

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

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

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

  • SchedulingConfiguration 接口

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

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

EnableScheduling注释

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

* @author Chris Beams * @since 3.1 * @see Scheduled * @see SchedulingConfiguration * @see SchedulingConfigurer * @see ScheduledTaskRegistrar * @see Trigger * @see ScheduledAnnotationBeanPostProcessor */

并直接在写了EnableScheduling 的demo
介绍任务的Spring的计划任务执行功能
spring源码深入理解 (二):定时任务管理-源码核心解析_第1张图片

任务注解解析器(ScheduledAnnotationBeanPostProcessor)

全篇注释

/**
*注册用@{@link Scheduled}注释的方法的Bean后处理器
*由{@link org.springframework.scheduling.TaskScheduler}根据
*通过注释提供的“fixedRate”、“fixedDelay”或“cron”表达式。
*

这个后处理器由Spring的 *{@代码}XML元素,以及 *@{@link EnableScheduling}注释。 *

自动检测容器中的任何{@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;
	}
  • 方法调用栈

spring源码深入理解 (二):定时任务管理-源码核心解析_第2张图片

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

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();
  • 方法调用栈

spring源码深入理解 (二):定时任务管理-源码核心解析_第3张图片

  • 其他一些小功能

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

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

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

在这里插入图片描述

定时任务注册器(ScheduledTaskRegistrar)

  • ScheduledTaskRegistrar全篇注释
/**
*用于向{@link TaskScheduler}注册任务的Helper bean,通常使用cron
*表达式。
*

从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销毁时,任务自动销毁。 包括各个任务的注册

spring源码深入理解 (二):定时任务管理-源码核心解析_第4张图片

  • 也是实现了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);
		}
  • 调用方法栈

spring源码深入理解 (二):定时任务管理-源码核心解析_第5张图片

任务调度器(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 ,并且单线程)

spring源码深入理解 (二):定时任务管理-源码核心解析_第6张图片
调用方法栈
spring源码深入理解 (二):定时任务管理-源码核心解析_第7张图片
通过 设置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);
		}
	}

  • 其他小功能

包括提供活跃数获取等
spring源码深入理解 (二):定时任务管理-源码核心解析_第8张图片

由返回值的线程
spring源码深入理解 (二):定时任务管理-源码核心解析_第9张图片

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

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

/**
*内部适配器,根据
*到给定的{@link Trigger}建议的下一个执行时间。
*

因为本机{@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中的线程池执行定时任务的类

spring源码深入理解 (二):定时任务管理-源码核心解析_第10张图片

ScheduledFutureTask 工作原理如下图所示

spring源码深入理解 (二):定时任务管理-源码核心解析_第11张图片
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 工作原理图画。

你可能感兴趣的:(spring,spring,java,定时任务)