浅谈Spring中定时任务@Scheduled源码的解析(二)

2024-07-25 10:29:56 浏览数 (2)

浅谈Spring中定时任务@Scheduled源码的解析(二)

一、介绍

在上一篇文章中,我们知道了,spring是如何获取到task

那么本篇将简单解读我们是如何将这些task运行起来的

二、如何运行

上面的代码只是讲述了如何获取到task,那么接下来如何将这些task当成定时任务来执行呢

我们接着往下看,还是当前这个类,实现了ApplicationListener<ContextRefreshedEvent>

这也就代表着在容器启动完成后,会调用这个方法void onApplicationEvent(E event);

代码语言:javascript复制
@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();那就继续看

代码语言:javascript复制
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();
}
  1. 首先判断scheduler存不存在,这个是肯定不在的,我们跳过这个判断
  2. 再然后注意,它获取了什么,SchedulingConfigurer.java这个bean有印象不。我们在前面设置自己的线程池时,实现了这个类。
    1. 所以这里获取到我们的bean,并执行configurer.configureTasks(this.registrar);
    2. 将我们的线程池,设置到registrar注册器中
  3. 再后来判断如果有任务,且没有调度器的话
    1. 尝试在beanFactory中查找TaskScheduler类型的bean
      1. 若找到多个,尝试通过名称'taskScheduler'解决
      2. 若找不到,尝试查找ScheduledExecutorService类型的bean,并重复上述逻辑
    2. 若真的找不到,会使用registrar的默认调度器
  4. 最后调用注册器this.registrar.afterPropertiesSet();
代码语言:javascript复制
@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();

triggerTaskscronTasksfixedRateTasksfixedDelayTasks

这四个集合,若是里面有任务,将循环着将任务添加到调度器中,我们以这个方法为例

addScheduledTask(scheduleCronTask(task));

代码语言:javascript复制
@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);
}
  1. 首先从unresolvedTasks中移除任务,给予变量scheduledTask
  2. 判断这个任务存不存在
    1. 如果为空,则新建一个
  3. 判断任务调度器,如果存在
    1. 用调度器调用方法,传入任务的runnable、以及任务的trigger
  4. 判断任务调度器,如果不存在
    1. 那么任务将被标记为待处理,存储在unresolvedTasks
    2. 等到时候有了调度器,就能运行了

四、最后

那么,spring的定时任务源码,就先解读到这边了

注意,上面讲到了spring的定时任务默认的线程池是单线程的

到时候面试的时候,不要忘记了

0 人点赞