Spring Schedule源码解析

Spring Schedule

一般简单的定时任务需要使用Spring的Scheduled执行比较简单,但是如果不了解原理,可能会入坑。下面简单的从源码出发介绍下;有兴趣可以在标识处打个断点调试下。

概论

Spring的Scheduled内部实现就是将方法体构造成Runnable,根据注解中不同配置构造,在内部调用JUC的ScheduledExecutorService的API去实现,这里有个问题就是ScheduledThreadPoolExecutor的线程数配置几个,如果配置的不对,对时间敏感性的需求是有问题的,直接表现为定时任务没有按指定时间运行;根本原因在于默认配置,内部只有一个线程执行。

解决方式按需求配置多个线程执行,配置方式有两种:

第一种 配置Bean

@Bean("taskScheduler") 
public TaskScheduler taskScheduler() {
    return new ConcurrentTaskScheduler(Executors.newScheduledThreadPool(5));
}

第二种 实现SchedulingConfigurer

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    ConcurrentTaskScheduler concurrentTaskScheduler = new ConcurrentTaskScheduler(Executors.newScheduledThreadPool(5));
    taskRegistrar.setTaskScheduler(concurrentTaskScheduler);
}

如果两种都存在,根据下面源码分析知道第二种生效

ScheduledAnnotationBeanPostProcessor

通过EnableScheduling找到SchedulingConfiguration,定位到ScheduledAnnotationBeanPostProcessor
注:配置类 项目里的定时任务配置QuartzConfig, 后面有用
下面代码已经删除无关逻辑, 基于Spring 5.12

@Slf4j
@Configuration
@EnableScheduling
public class QuartzConfig implements SchedulingConfigurer {
     //配置方式1
    // @Bean("taskScheduler")  //name=taskScheduler 必须?
    // public TaskScheduler taskScheduler() {
    //     return new ConcurrentTaskScheduler(Executors.newScheduledThreadPool(5));
    // }

    /**
     * 配置方式2
     * @param taskRegistrar the registrar to be configured.
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ConcurrentTaskScheduler concurrentTaskScheduler = new ConcurrentTaskScheduler(Executors.newScheduledThreadPool(5));
        taskRegistrar.setTaskScheduler(concurrentTaskScheduler);
        //注释这个原理是一样的
        //taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
    }

    @Scheduled(fixedRate = 1000 * 20)
    public void scheduledTaskA20() {
       log.error("任务A每20秒执行一次");
        try {
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            log.error("InterruptedException A");
        }
    }

    @Scheduled(fixedRate = 1000 * 10)
    public void scheduledTaskB10() {
        log.error("任务B每10秒执行一次");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            log.error("InterruptedException B");
        }
    }
}

方法: postProcessAfterInitialization

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
	Class targetClass = AopProxyUtils.ultimateTargetClass(bean);
	if (!this.nonAnnotatedClasses.contains(targetClass)) {
	    //1.
		Map> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
				(MethodIntrospector.MetadataLookup>) method -> {
					Set scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
							method, Scheduled.class, Schedules.class);
					return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
				});
		if (!annotatedMethods.isEmpty()) {
			annotatedMethods.forEach((method, scheduledMethods) ->
			        //2.
					schedudMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));  
		}
	}
	return bean;
}

1、找到配置类里所有Scheduled注解的方法
2、调用processScheduled将方法封装成对应的定时任务类型

方法:processScheduled

protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
		//3.
		Runnable runnable = createRunnable(bean, method);
		boolean processedSchedule = false;
		Set tasks = new LinkedHashSet<>(4);

		//4.1 CronTask
		String cron = scheduled.cron();
		if (StringUtils.hasText(cron)) {
		    //...
			tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
		}
        //4.2 FixedDelayTask
		long fixedDelay = scheduled.fixedDelay();
		if (fixedDelay >= 0) {
			tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
		}
		//4.3 FixedDelayTask
		String fixedDelayString = scheduled.fixedDelayString();
		if (StringUtils.hasText(fixedDelayString)) {
			fixedDelay = parseDelayAsLong(fixedDelayString);
			tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
		}

		//4.4 FixedRateTask
		long fixedRate = scheduled.fixedRate();
		if (fixedRate >= 0) {
			Assert.isTrue(!processedSchedule, errorMessage);
			processedSchedule = true;
			tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
		} 
		//4.5 FixedRateTask
		String fixedRateString = scheduled.fixedRateString();
		if (StringUtils.hasText(fixedRateString)) {
			fixedRate = parseDelayAsLong(fixedRateString);
			tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
		}
		//5.
		synchronized (this.scheduledTasks) {
			Set regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
			regTasks.addAll(tasks);
		}
	}
}

3、将配置类里的逻辑转换成Runnable(ScheduledMethodRunnable)
4.x、分析注解对象,转换成对应的定时任务,如下图

Spring Schedule源码解析_第1张图片
5、处理完后交给ScheduledTaskRegistrar

Spring的onApplicationEvent触发finishRegistration

方法:finishRegistration

private void finishRegistration() {
	if (this.scheduler != null) {
		this.registrar.setScheduler(this.scheduler);
	}

	if (this.beanFactory instanceof ListableBeanFactory) {
		Map beans =
				((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
		List configurers = new ArrayList<>(beans.values());
		AnnotationAwareOrderComparator.sort(configurers);
		for (SchedulingConfigurer configurer : configurers) {
		    //6.
			configurer.configureTasks(this.registrar);
		}
	}

	if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
		try {
		    //7.
			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));
				}
			}
		}
	}
	//9.触发定时任务生效
	this.registrar.afterPropertiesSet();
}

private  T resolveSchedulerBean(BeanFactory beanFactory, Class schedulerType, boolean byName) {
	if (byName) {
		T scheduler = beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, schedulerType);
		if (this.beanName != null && this.beanFactory instanceof ConfigurableBeanFactory) {
			((ConfigurableBeanFactory) this.beanFactory).registerDependentBean(
					DEFAULT_TASK_SCHEDULER_BEAN_NAME, this.beanName);
		}
		return scheduler;
	}
	//8.
	else if (beanFactory instanceof AutowireCapableBeanFactory) {
		NamedBeanHolder holder = ((AutowireCapableBeanFactory) beanFactory).resolveNamedBean(schedulerType);
		if (this.beanName != null && beanFactory instanceof ConfigurableBeanFactory) {
			((ConfigurableBeanFactory) beanFactory).registerDependentBean(holder.getBeanName(), this.beanName);
		}
		return holder.getBeanInstance();
	}
	else {
		return beanFactory.getBean(schedulerType);
	}
}

finishRegistration主要负责配置TaskScheduler的;
6. 如果实现SchedulingConfigurer接口的方式配置TaskScheduler,则在此处生效
7.如果通过配置TaskScheduler的Bean的形式,会在8.处找到配置的Bean,进而成效
如果没有配置用什么?见ScheduledTaskRegistrar的分析

ScheduledTaskRegistrar

通过ScheduledAnnotationBeanPostProcessor的processScheduled方法,ScheduledTaskRegistrar会得到各种任务的集合,再由ScheduledAnnotationBeanPostProcessor触发定时任务的执行;见9.处注释。

获取定时任务

以配置类里的固定频率执行的任务来看主要逻辑,其他类型定时任务也是类似的。

public ScheduledTask scheduleFixedRateTask(FixedRateTask task) {
	ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
	boolean newTask = false;
	if (scheduledTask == null) {
		scheduledTask = new ScheduledTask(task);
		newTask = true;
	}
	// 上面两种配置方式,第一次到这里taskScheduler是null
	if (this.taskScheduler != null) {
		if (task.getInitialDelay() > 0) {
			Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
			//13.
			scheduledTask.future =
					this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval()); 
		}
		else {
			scheduledTask.future =
					this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), task.getInterval());
		}
	}
	else {
	    //10.
		addFixedRateTask(task);
		this.unresolvedTasks.put(task, scheduledTask);
	}
	return (newTask ? scheduledTask : null);
}

10、将定时任务添加到fixedRateTasks集合
13、将定时任务交给ScheduledExecutorService执行

11、从这里可以看到,如果没有配置TaskScheduler,默认的是单线程的ConcurrentTaskScheduler
12、以fixedRateTasks来说,经过finishRegistration()配置好TaskScheduler后,可以触发定时任务的执行了
内部还是执行scheduleFixedRateTask()的逻辑。

到这里所有的逻辑基本走完了,下面简单说一下定时任务的执行容器ConcurrentTaskScheduler。

ConcurrentTaskScheduler

内部有一个JUC 的ScheduledExecutorService scheduledExecutor,根据参数配置;
对应各种类型的任务使用scheduledExecutor的API:

ScheduledFuture schedule(Runnable command, ong delay, TimeUnit unit);
ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
ScheduledFuture scheduleWithFixedDelay(Runnable command,long initialDelay, long delay, TimeUnit unit);

默认配置和配置线程之后效果对比:

2020-01-12 19:56:51.898 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 19:57:01.904 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 19:57:11.905 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig                   : 任务A每20秒执行一次
2020-01-12 19:57:31.911 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 19:57:42.166 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 19:57:52.170 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig                   : 任务A每20秒执行一次
2020-01-12 19:58:12.173 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 19:58:22.179 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 19:58:32.184 ERROR 33017 [ool-19-thread-1] c.h.s.m.c.QuartzConfig                   : 任务A每20秒执行一次


2020-01-12 20:13:37.756 ERROR 35810 [taskScheduler-3] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 20:13:47.752 ERROR 35810 [taskScheduler-2] c.h.s.m.c.QuartzConfig                   : 任务A每20秒执行一次
2020-01-12 20:13:47.764 ERROR 35810 [taskScheduler-4] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 20:13:57.766 ERROR 35810 [taskScheduler-5] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 20:14:07.758 ERROR 35810 [taskScheduler-6] c.h.s.m.c.QuartzConfig                   : 任务A每20秒执行一次
2020-01-12 20:14:07.769 ERROR 35810 [taskScheduler-7] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 20:14:17.774 ERROR 35810 [taskScheduler-1] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 20:14:27.761 ERROR 35810 [taskScheduler-4] c.h.s.m.c.QuartzConfig                   : 任务A每20秒执行一次
2020-01-12 20:14:27.780 ERROR 35810 [taskScheduler-9] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 20:14:37.786 ERROR 35810 [taskScheduler-5] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次
2020-01-12 20:14:47.766 ERROR 35810 [taskScheduler-8] c.h.s.m.c.QuartzConfig                   : 任务A每20秒执行一次
2020-01-12 20:14:47.790 ERROR 35810 [askScheduler-11] c.h.s.m.c.QuartzConfig                   : 任务B每10秒执行一次

Spring Schedule的问题

1、不支持动态修改定时任务的执行频率;
2、不直接支持分布式部署;
3、如上分析,面对不对变化的需求,线程池大小配置是个问题;

如何解决,用Quartz吧。

你可能感兴趣的:(Spring)