Spring-Schedule定时任务调度源码分析

本文详细解读了Spring定时任务的实现原理,从@EnableScheduling到ScheduledAnnotationBeanPostProcessor,教你如何定制任务调度模式,包括cron表达式解析和线程池配置。
摘要由CSDN通过智能技术生成

        定时任务是我们平常业务开发中常用的工具,凌晨统计数据,清理历史日志等等。spring非常友好的给我们提供了定时任务调度基础设施。

我们平常经常这样配置定时任务:

@EnableScheduling // 开启定时任务
@Component
public class Sch01 {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    @Scheduled(cron = "* * * * * ?")
    public void sch001() {

        logger.info("hello 1");
    }
}

效果很容易达到:

2021-07-21 22:23:55.015 INFO 255 - [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:23:56.015 INFO 255 - [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:23:57.004 INFO 255 - [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:23:58.009 INFO 255 - [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:23:59.015 INFO 255 - [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
....  

简单的一个cron表达式就能轻松搞定,但是背后的原理也是值得分析,并且做到定制话自己的任务调度模式。

          @EnableScheduling作为定时调度的开关,很符合spring的编程风格

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}

        其最主要的目的就是import配置类SchedulingConfiguration

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

	@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
		return new ScheduledAnnotationBeanPostProcessor();
	}
}

//简单注意下这个beanPostProcessor,其实现了很多接口,但是都相对熟悉
public class ScheduledAnnotationBeanPostProcessor
		implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
		Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
		SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {

	/**
	 * Create a default {@code ScheduledAnnotationBeanPostProcessor}.
	 */
	public ScheduledAnnotationBeanPostProcessor() {
        //设置任务注册中心,可以理解为一个容器,存放解析@Scheduled注解的方法解析后的信息
		this.registrar = new ScheduledTaskRegistrar();
	}
//....
}

可见主要配置了一个BeanPostProcessor,在每个bean创建生命周期中postProcessAfterInitialization会调被用。

public Object postProcessAfterInitialization(Object bean, String beanName) {

	//忽略aop切面类,TaskScheduler及ScheduledExecutorService执行器类本身
	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 {
			
			// 存在定时任务注解,对每个方法进行解析
			annotatedMethods.forEach((method, scheduledAnnotations) ->
            //解析processScheduled
					scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
			if (logger.isTraceEnabled()) {
				logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
						"': " + annotatedMethods);
			}
		}
	}
	return bean;
}

跟踪processScheduled方法

protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
	try {
		//对方法进行封装为ScheduledMethodRunnable
		Runnable runnable = createRunnable(bean, method);
		boolean processedSchedule = false;
		String errorMessage =
				"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";

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

		// 解析起始延迟时间 @Scheduled(initialDelay = 5000L, cron = "* * * * * ?")
		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");
				}
			}
		}

		// 解析cron表达式模式 @Scheduled(initialDelay = 5000L, 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);
			}
			//解析cron表达式
			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();
					}
					
					//包装并添加任务到任务注册中心
					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;
		}

		// 解析延迟类型任务,同cron表达式一样,不在赘述
		long fixedDelay = scheduled.fixedDelay();
		if (fixedDelay >= 0) {
			Assert.isTrue(!processedSchedule, errorMessage);
			processedSchedule = true;
			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");
				}
				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");
				}
				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());
	}
}

        继续注册中心ScheduledTaskRegistrar的添加方法:

public ScheduledTask scheduleCronTask(CronTask task) {
	//检查未解析的缓存
	ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
	boolean newTask = false;
	//包装
	if (scheduledTask == null) {
		scheduledTask = new ScheduledTask(task);
		newTask = true;
	}
	//
	if (this.taskScheduler != null) {
		scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
	}
	else {
		//添加到待解析处理容器
		addCronTask(task);
		this.unresolvedTasks.put(task, scheduledTask);
	}
	return (newTask ? scheduledTask : null);
}


public void addCronTask(CronTask task) {
	if (this.cronTasks == null) {
		this.cronTasks = new ArrayList<>();
	}
	this.cronTasks.add(task);
}

        我们可以近距离的看下注册中心有哪些容器

public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean {

	/**
	 * A special cron expression value that indicates a disabled trigger: {@value}.
	 * <p>This is primarily meant for use with {@link #addCronTask(Runnable, String)}
	 * when the value for the supplied {@code expression} is retrieved from an
	 * external source &mdash; for example, from a property in the
	 * {@link org.springframework.core.env.Environment Environment}.
	 * @since 5.2
	 * @see org.springframework.scheduling.annotation.Scheduled#CRON_DISABLED
	 */
	public static final String CRON_DISABLED = "-";


	@Nullable
	private TaskScheduler taskScheduler;

	@Nullable
	private ScheduledExecutorService localExecutor;

	@Nullable
	private List<TriggerTask> triggerTasks;

	@Nullable
	private List<CronTask> cronTasks;

	@Nullable
	private List<IntervalTask> fixedRateTasks;

	@Nullable
	private List<IntervalTask> fixedDelayTasks;

	private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<>(16);

	private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet<>(16);

        可见该类就是分类的多个容器,里面存储着解析后的任务对象,该类实现了InitializingBean接口,在生命周期中会执行afterPropertiesSet方法:

public void afterPropertiesSet() {
	scheduleTasks();
}


protected void scheduleTasks() {
	//若调度器为指定
	if (this.taskScheduler == null) {
		//默认的线程池是单线程线程池
		this.localExecutor = Executors.newSingleThreadScheduledExecutor();
		this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
	}
	
	//真正的添加待执行任务
	if (this.triggerTasks != null) {
		for (TriggerTask task : this.triggerTasks) {
			addScheduledTask(scheduleTriggerTask(task));
		}
	}
	if (this.cronTasks != null) {
		for (CronTask task : this.cronTasks) {
			addScheduledTask(scheduleCronTask(task));
		}
	}
	if (this.fixedRateTasks != null) {
		for (IntervalTask task : this.fixedRateTasks) {
			addScheduledTask(scheduleFixedRateTask(task));
		}
	}
	if (this.fixedDelayTasks != null) {
		for (IntervalTask task : this.fixedDelayTasks) {
			addScheduledTask(scheduleFixedDelayTask(task));
		}
	}
}

        我们可以测试下默认情况是否是单线程:

        新增定时任务2

@Component
public class Sch02 {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    @Scheduled(cron = "* * * * * ?")
    public void sch002() {
        logger.info("hello 2");
    }
}

        启动后输出:发现是同一个线程在执行[pool-1-thread-1]

2021-07-21 22:27:17.004  INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch02 : hello 2
2021-07-21 22:27:17.004  INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:27:18.006  INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch02 : hello 2
2021-07-21 22:27:18.006  INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:27:19.006  INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch02 : hello 2
2021-07-21 22:27:19.006  INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
....

        发现是同一个线程在执行[pool-1-thread-1],可见平时生产开发中一定要注意配置自定义线程池来执行定时任务,特别是对于io密集型任务,若是单线程则可能阻塞其他任务无法执行。

我们继续回过头看看ScheduledAnnotationBeanPostProcessor类,其实现了SmartInitializingSingleton在生命周期中会调用afterSingletonsInstantiated方法。

public void afterSingletonsInstantiated() {
	// Remove resolved singleton classes from cache
	this.nonAnnotatedClasses.clear();

	if (this.applicationContext == null) {
		// Not running in an ApplicationContext -> register tasks early...
		finishRegistration();
	}
}

重点是finishRegistration方法的执行

private void finishRegistration() {
	//设置调度器
	if (this.scheduler != null) {
		this.registrar.setScheduler(this.scheduler);
	}

	if (this.beanFactory instanceof ListableBeanFactory) {
		//查找扩展配置类SchedulingConfigurer
		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);
		}
	}

	if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
		Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
		try {
			// Search for TaskScheduler bean...
			this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
		}
		catch (NoUniqueBeanDefinitionException ex) {
			try {
				this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
			}
			catch (NoSuchBeanDefinitionException ex2) {
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			
			try {
				this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
			}
			catch (NoUniqueBeanDefinitionException ex2) {
				
				try {
					this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
				}
				catch (NoSuchBeanDefinitionException ex3) {
					
				}
			}
			catch (NoSuchBeanDefinitionException ex2) {
				
				// Giving up -> falling back to default scheduler within the registrar...
				logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
			}
		}
	}
	
	//开启注册中心的调度任务
	this.registrar.afterPropertiesSet();
}

        这里重点在于开放了任务注册中心的扩展点SchedulingConfigurer,我们可以对任务注册中心进行自定义修改,如执行任务的线程池,调度器,添加任务,获取任务,甚至可以反射删除任务等等,详见:彻底关闭Schedule定时任务​​​​​​​

示例:自定义任务调度器,配置线程池

@Component
public class SchConf implements SchedulingConfigurer {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        Set<ScheduledTask> scheduledTasks = taskRegistrar.getScheduledTasks();
        logger.info("所有任务: {}", scheduledTasks);
        //设置调度器
        ConcurrentTaskScheduler taskScheduler = new ConcurrentTaskScheduler();
        taskScheduler.setScheduledExecutor(Executors.newScheduledThreadPool(20));
        taskRegistrar.setScheduler(taskScheduler);
    }
}

最后:

        在平时的生产开发中,现阶段已经流行微服务集群等概念,项目内直接配置定时任务的方式愈发少见了,而是采用一些三方任务调度框架如xxl-job,elastic-job等等有更好的分布式支持特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值