浅谈Spring中定时任务@Scheduled源码的解析(二)
一、介绍
在上一篇文章中,我们知道了,spring
是如何获取到task
的
那么本篇将简单解读我们是如何将这些task
运行起来的
二、如何运行
上面的代码只是讲述了如何获取到task
,那么接下来如何将这些task
当成定时任务来执行呢
我们接着往下看,还是当前这个类,实现了ApplicationListener<ContextRefreshedEvent>
这也就代表着在容器启动完成后,会调用这个方法void onApplicationEvent(E event);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.applicationContext) {
// Running in an ApplicationContext -> register tasks this late...
// giving other ContextRefreshedEvent listeners a chance to perform
// their work at the same time (e.g. Spring Batch's job registration).
finishRegistration();
}
}
里面调用了这个方法finishRegistration();
那就继续看
private void finishRegistration() {
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
}
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);
}
}
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) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique TaskScheduler bean - attempting to resolve by name: "
ex.getMessage());
}
try {
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isInfoEnabled()) {
logger.info("More than one TaskScheduler bean exists within the context, and "
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' "
"(possibly as an alias); or implement the SchedulingConfigurer interface and call "
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: "
ex.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default TaskScheduler bean - attempting to find ScheduledExecutorService: "
ex.getMessage());
}
// Search for ScheduledExecutorService bean next...
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
}
catch (NoUniqueBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique ScheduledExecutorService bean - attempting to resolve by name: "
ex2.getMessage());
}
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
}
catch (NoSuchBeanDefinitionException ex3) {
if (logger.isInfoEnabled()) {
logger.info("More than one ScheduledExecutorService bean exists within the context, and "
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' "
"(possibly as an alias); or implement the SchedulingConfigurer interface and call "
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: "
ex2.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default ScheduledExecutorService bean - falling back to default: "
ex2.getMessage());
}
// Giving up -> falling back to default scheduler within the registrar...
logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
}
}
}
this.registrar.afterPropertiesSet();
}
- 首先判断
scheduler
存不存在,这个是肯定不在的,我们跳过这个判断 - 再然后注意,它获取了什么,
SchedulingConfigurer.java
这个bean
有印象不。我们在前面设置自己的线程池时,实现了这个类。- 所以这里获取到我们的
bean
,并执行configurer.configureTasks(this.registrar);
- 将我们的线程池,设置到
registrar
注册器中
- 所以这里获取到我们的
- 再后来判断如果有任务,且没有调度器的话
- 尝试在
beanFactory
中查找TaskScheduler
类型的bean- 若找到多个,尝试通过名称'taskScheduler'解决
- 若找不到,尝试查找
ScheduledExecutorService
类型的bean,并重复上述逻辑
- 若真的找不到,会使用
registrar
的默认调度器
- 尝试在
- 最后调用注册器
this.registrar.afterPropertiesSet();
@Override
public void afterPropertiesSet() {
scheduleTasks();
}
代码语言:javascript复制@SuppressWarnings("deprecation")
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));
}
}
}
来到scheduleTasks()
,这个方法就是将任务加入到调度器了
可以看到上面,如果没有调度器的话 它是自己生成一个单线程的线程池,作为调度器
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
triggerTasks
、cronTasks
、fixedRateTasks
、fixedDelayTasks
这四个集合,若是里面有任务,将循环着将任务添加到调度器中,我们以这个方法为例
addScheduledTask(scheduleCronTask(task));
@Nullable
public ScheduledTask scheduleTriggerTask(TriggerTask 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 {
addTriggerTask(task);
this.unresolvedTasks.put(task, scheduledTask);
}
return (newTask ? scheduledTask : null);
}
- 首先从
unresolvedTasks
中移除任务,给予变量scheduledTask
- 判断这个任务存不存在
- 如果为空,则新建一个
- 判断任务调度器,如果存在
- 用调度器调用方法,传入任务的
runnable
、以及任务的trigger
- 用调度器调用方法,传入任务的
- 判断任务调度器,如果不存在
- 那么任务将被标记为待处理,存储在
unresolvedTasks
中 - 等到时候有了调度器,就能运行了
- 那么任务将被标记为待处理,存储在
四、最后
那么,spring
的定时任务源码,就先解读到这边了
注意,上面讲到了spring
的定时任务默认的线程池是单线程的
到时候面试的时候,不要忘记了