Spring中@EnableScheduling注解的工作原理详解

 更新时间:2024年01月25日 09:49:43   作者:安迪源文  
这篇文章主要介绍了Spring中@EnableScheduling注解的工作原理详解,@EnableScheduling是 Spring Framework 提供的一个注解,用于启用Spring的定时任务(Scheduling)功能,需要的朋友可以参考下

概述

  1. 开发人员使用注解 @EnableScheduling;
  2. 注解@EnableScheduling导入SchedulingConfiguration;
  3. SchedulingConfiguration定义基础设施bean ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor;
  4. ScheduledAnnotationBeanPostProcessor在容器启动时做如下事情
    • 登记所有使用@Scheduled注解的bean方法到一个ScheduledTaskRegistrar,供调度任务执行器TaskScheduler执行。
    • 为ScheduledTaskRegistrar指定任务执行器TaskScheduler,该任务执行器来自容器中的bean TaskScheduler/ScheduledExecutorService(如果不指定,ScheduledTaskRegistrar自己会本地创建一个ConcurrentTaskScheduler)
    • 告诉ScheduledTaskRegistrar将所注册的调度任务,也就是使用@Scheduled注解的bean方法,调度到任务执行器TaskScheduler执行。

详细分析

开发人员注解@EnableScheduling

@EnableScheduling // <==
@SpringBootApplication
public class Application {
    public static void main(String[] args)  {
        SpringApplication.run(Application.class, args);
    }
}

@EnableScheduling注解导入配置@SchedulingConfiguration

@Import(SchedulingConfiguration.class)  // <==
public @interface EnableScheduling {
}

SchedulingConfiguration 定义bean scheduledAnnotationProcessor

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {

    // Bean 名称使用 : 
    // org.springframework.context.annotation.internalScheduledAnnotationProcessor
	@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE) // 定义为基础设施bean
	public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
		return new ScheduledAnnotationBeanPostProcessor();
	}

}

ScheduledAnnotationBeanPostProcessor#postProcessAfterInitialization检测处理每个@Scheduled注解的方法 ScheduledAnnotationBeanPostProcessor实现了DestructionAwareBeanPostProcessor,BeanPostProcessor等接口。作为一个BeanPostProcessor,ScheduledAnnotationBeanPostProcessor会针对每个bean的创建,在bean生命周期方法#postProcessAfterInitialization中,扫描该bean中使用了注解@Scheduled的方法,

@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);
       // this.nonAnnotatedClasses 是一个缓存,用于记录处理过程中所发现的不包含任何被@Scheduled注解的方法的类
		if (!this.nonAnnotatedClasses.contains(targetClass)) {
          // 获取类  targetClass 上所有使用注解  @Scheduled 的方法
          // 注意 : 某个方法上可能同时使用多个注解  @Scheduled ,所以以下 annotatedMethods 的每个 Entry 是
          // 一个方法对应一个 @cheduled 集合
			Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
					(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
						Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
								method, Scheduled.class, Schedules.class);
						return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
					});
			if (annotatedMethods.isEmpty()) {
              // 如果当前类 targetClass 不包含任何使用注解  @Scheduled 的方法,将其添加到 this.nonAnnotatedClasses
				this.nonAnnotatedClasses.add(targetClass);
				if (logger.isTraceEnabled()) {
					logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
				}
			}
			else {
				// Non-empty set of methods
              // 当前类 targetClass 上找到了使用注解 @Scheduled 的方法,记录在  annotatedMethods 中,
              // 现在将它们逐个处理,使用的处理为方法 processScheduled             
				annotatedMethods.forEach((method, scheduledMethods) ->
						scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
				if (logger.isTraceEnabled()) {
					logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
							"': " + annotatedMethods);
				}
			}
		}
		return bean;
	}

#rocessScheduled处理方法上的每个@Scheduled注解,生成一个ScheduledTask并登记到this.scheduledTasks。this.scheduledTasks数据结构为 :

  • Map数据类型;
    • key是一个对象,其类就是含有方法使用了注解@Scheduled的类;
    • value是一个ScheduledTask集合,方法上的每个注解@Scheduled对应一个ScheduledTask;

实例分析

举例来讲,加入组件MyScheduledTask类中有两个方法#method1,#method2上一用使用了五个注解Scheduled,则this.scheduledTasks会出现一项针对组件MyScheduledTask bean的项,key是组件MyScheduledTask bean对象自身,value是五个ScheduledTask。

/**
	 * Process the given {@code @Scheduled} method declaration on the given bean.
	 * @param scheduled the @Scheduled annotation
	 * @param method the method that the annotation has been declared on
	 * @param bean the target bean instance
	 * @see #createRunnable(Object, Method)
	 */
	protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
		try {
           // 将使用了 @Scheduled 注解的方法包装成一个 Runnable 对象  , 随后构建 ScheduledTask 对象时
           // 会用得到
			Runnable runnable = createRunnable(bean, method);
          // 用于记录当前 @Scheduled 注解是否已经被处理,初始化为 false  
			boolean processedSchedule = false;
			String errorMessage =
			"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
          // 用于保存针对当前 @Scheduled  注解生成的 ScheduledTask,
          // 该方法完成时,该集合内元素数量通常为 1
			Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
			// Determine initial delay
          // 确定 initial delay 属性 : 基于注解属性 initialDelay 或者  initialDelayString 分析得到,
          // 二者只能使用其中之一
			long initialDelay = scheduled.initialDelay();
			String initialDelayString = scheduled.initialDelayString();
			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");
					}
				}
			}
			// Check cron expression
          // 检查这是否是一个 cron 表达式类型的注解  
			String cron = scheduled.cron();
			if (StringUtils.hasText(cron)) {
				String zone = scheduled.zone();
				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 (!Scheduled.CRON_DISABLED.equals(cron)) {
						TimeZone timeZone;
						if (StringUtils.hasText(zone)) {
							timeZone = StringUtils.parseTimeZoneString(zone);
						}
						else {
							timeZone = TimeZone.getDefault();
						}
	                    // 包装成为一个 CronTask 
						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 = scheduled.fixedDelay();
			if (fixedDelay >= 0) {
				Assert.isTrue(!processedSchedule, errorMessage);
				processedSchedule = true;
				// 包装成为一个 FixedDelayTask 
				tasks.add(this.registrar.scheduleFixedDelayTask(
					new FixedDelayTask(runnable, fixedDelay, initialDelay)));
			}
			String fixedDelayString = scheduled.fixedDelayString();
			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");
					}
	                 // 包装成为一个 FixedDelayTask    
					tasks.add(this.registrar.scheduleFixedDelayTask(
						new FixedDelayTask(runnable, fixedDelay, initialDelay)));
				}
			}
			// Check fixed rate
           // 检查这是否是一个固定周期执行类型的注解    
			long fixedRate = scheduled.fixedRate();
			if (fixedRate >= 0) {
				Assert.isTrue(!processedSchedule, errorMessage);
				processedSchedule = true;
				tasks.add(this.registrar.scheduleFixedRateTask(
					new FixedRateTask(runnable, fixedRate, initialDelay)));
			}
			String fixedRateString = scheduled.fixedRateString();
			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");
					}
					// 包装成为一个 FixedRateTask       
					tasks.add(this.registrar.scheduleFixedRateTask(
						new FixedRateTask(runnable, fixedRate, initialDelay)));
				}
			}
			// Check whether we had any attribute set
			Assert.isTrue(processedSchedule, errorMessage);
			// Finally register the scheduled tasks
			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());
		}
	}

经过ScheduledAnnotationBeanPostProcessor以上这些处理,每个bean中所包含的@Scheduled注解都被发现了,这样的每条信息最终对应生成一个ScheduledTask,该ScheduledTask会被ScheduledTaskRegistrar registrar登记调度。这意味着该ScheduledTask从此刻起在程序运行期间就会按照@Scheduled注解所设定的时间点被执行。

到此这篇关于Spring中@EnableScheduling注解的工作原理详解的文章就介绍到这了,更多相关@EnableScheduling注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Intellj idea新建的java源文件夹不是蓝色的图文解决办法

    Intellj idea新建的java源文件夹不是蓝色的图文解决办法

    idea打开java项目后新建的模块中,java文件夹需要变成蓝色,这篇文章主要给大家介绍了关于Intellj idea新建的java源文件夹不是蓝色的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2024-02-02
  • Java @Autowired注解底层原理详细分析

    Java @Autowired注解底层原理详细分析

    @Autowired注解可以用在类属性,构造函数,setter方法和函数参数上,该注解可以准确地控制bean在何处如何自动装配的过程。在默认情况下,该注解是类型驱动的注入
    2022-11-11
  • Mybatis Plus整合PageHelper分页的实现示例

    Mybatis Plus整合PageHelper分页的实现示例

    这篇文章主要介绍了Mybatis Plus整合PageHelper分页的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Spring Boot 简介(入门篇)

    Spring Boot 简介(入门篇)

    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。下面通过本文给大家介绍spring boot相关知识,需要的的朋友参考下吧
    2017-04-04
  • Spring Security自定义认证逻辑实例详解

    Spring Security自定义认证逻辑实例详解

    这篇文章主要给大家介绍了关于Spring Security自定义认证逻辑的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-01-01
  • spring boot 本地图片不能加载(图片路径)的问题及解决方法

    spring boot 本地图片不能加载(图片路径)的问题及解决方法

    这篇文章主要介绍了spring boot 本地图片不能加载(图片路径)的问题,解决的办法其实很简单,只要写一个配置文件,也就是图片位置的转化器,原理是虚拟一个在服务器上的文件夹,与本地图片的位置进行匹配。需要的朋友可以参考下
    2018-04-04
  • Java CountDownLatch计数器与CyclicBarrier循环屏障

    Java CountDownLatch计数器与CyclicBarrier循环屏障

    CountDownLatch是一种同步辅助,允许一个或多个线程等待其他线程中正在执行的操作的ASET完成。它允许一组线程同时等待到达一个共同的障碍点
    2023-04-04
  • SpringBoot实现的Mongodb管理工具使用解析

    SpringBoot实现的Mongodb管理工具使用解析

    这篇文章主要介绍了SpringBoot实现的Mongodb管理工具使用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Netty粘包拆包及使用原理详解

    Netty粘包拆包及使用原理详解

    Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序,这篇文章主要介绍了Netty粘包拆包及使用原理
    2022-08-08
  • Java高性能新一代构建工具Maven-mvnd(实践可行版)

    Java高性能新一代构建工具Maven-mvnd(实践可行版)

    这篇文章主要介绍了Java高性能新一代构建工具Maven-mvnd(实践可行版),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06

最新评论