从EnableScheduling说起

在之前一篇文章Spring Boot定时任务,我们已经了解了如何在Spring Boot中创建并管理定时任务,今天我们通过源码来看下Spring Boot中的具体实现。

EnableScheduling注解

要想在Spring Boot中创建定时任务,就必须要加上这个注解,那么这个注解到底起到什么作用呢?首先来看下EnableScheduling的详细定义

/* @see Scheduled
 * @see SchedulingConfiguration
 * @see SchedulingConfigurer
 * @see ScheduledTaskRegistrar
 * @see Trigger
 * @see ScheduledAnnotationBeanPostProcessor
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}

可以看到EnableScheduling通过@import这个注解,引入了SchedulingConfiguration这个类。那么这个类又是干啥的呢?

SchedulingConfiguration

以下是SchedulingConfiguration的具体实现,可以看到这个类的主要目的在于注入了ScheduledAnnotationBeanPostProcessor这个类,那么这个类又干了啥呢?

@Configuration
@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();
    }

}

ScheduledAnnotationBeanPostProcessor


上述是对Spring Boot文档对ScheduledAnnotationBeanPostProcessor的描述,可以归纳为三点:

  1. ScheduledAnnotationBeanPostProcessor会通过TaskScheduler来调用所有标识了@Scheduled的方法
  2. ScheduledAnnotationBeanPostProcessor会自动检测容器中的任何SchedulingConfigurer实例
  3. 可以通过@EnableScheduling注解或者task:annotation-driven标签来注册ScheduledAnnotationBeanPostProcessor

下面我们来看下ScheduledAnnotationBeanPostProcessor是如何实现1,2两点的

postProcessAfterInitialization

ScheduledAnnotationBeanPostProcessor实现了BeanPostProcessor,了解Spring的同学可能会对这个类比较熟悉,它可以在Bean被实例化前后执行一些操作

public interface BeanPostProcessor {
    // 在实例化之前对bean进行处理
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    // 在实例化之后对bean进行处理
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

下图展示了postProcessAfterInitialization方法主要的实现逻辑

ScheduledAnnotationBeanPostProcessor通过postProcessAfterInitialization方法检测Bean中是否存在配置@Scheduled注解的方法。对于配置了@Scheduled注解的方法,ScheduledAnnotationBeanPostProcessor通过processScheduled方法做了进一步处理,我们来看下processScheduled的具体实现。

processScheduled

通过源码的注释,我们可以清晰的看到processScheduled的处理流程。

  1. 根据传入的Bean以及配置了@Scheduled注解的方法创建线程
  2. 解析@Scheduled的initialDelay和initialDelayString参数
  3. 若@Scheduled配置了cron参数,则解析cron并生成ScheduledTask
  4. 若@Scheduled配置了fixed rate或者fixed delay,则根据配置生成ScheduledTask
  5. 保存生成的ScheduledTask

ScheduledTaskRegistrar

到目前为止了,我们已经了解ScheduledAnnotationBeanPostProcessor会将配置了@Scheduled注解的方法转化为ScheduledTask。

其中ScheduledTaskRegistrar就负责根据配置生成的ScheduledTask。

可以看到ScheduledTaskRegistrar维护了各类型的Task,包括TriggerTask,CronTask,fixedRateTask,fixedDelayTask。最终这些Task都将转化为ScheduledTask

Scheduler

ScheduledTaskRegistrar负责维护ScheduledTask,那么该由谁来调度这些Task呢?答案就是Scheduler。

ScheduledAnnotationBeanPostProcessor在初始化的过程中会为ScheduledTaskRegistrar注入对应的Scheduler,具体流程就在finishRegistration方法中。


最终在resolveSchedulerBean方法中生成Scheduler

下面的流程图展示了具体过程

最终加载ScheduledTask,其实也就是调用了ScheduledTaskRegistrar中的scheduleTasks方法

我们可以看到如果ScheduledTaskRegistrar中的taskScheduler为null,它会生成一个SingleThreadScheduledExecutor作为taskScheduler。

SchedulingConfigurer

在介绍ScheduledAnnotationBeanPostProcessor的时候,我们提到过它会自动检测容器中的任何SchedulingConfigurer实例,那么SchedulingConfigurer实例又是起到什么作用的呢?

简单来说我们可以通过SchedulingConfigurer对ScheduledTaskRegistrar进行配置。例如我们可以指定ScheduledTaskRegistrar的taskScheduler,包括设置其线程池的大小等。

下面来看具体示例,我们首先定义一个测试类

@Component
@Slf4j
public class ScheduleTaskTest {

    @Scheduled(fixedDelay = 3000)
    public void print(){
        log.info("schedule task run");
    }
}

然后,我们实现SchedulingConfigurer接口,并自定义taskScheduler

@Component
public class MySchedulingConfigurer implements SchedulingConfigurer {

    private ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 设置线程池大小
        taskScheduler.setPoolSize(100);

        //设置线程工厂
        ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setNameFormat("scheduler-%d")
                .build();
        taskScheduler.setThreadFactory(threadFactory);

        taskScheduler.initialize();
        // 设置自定义的taskScheduler
        taskRegistrar.setTaskScheduler(taskScheduler);
    }
}

运行程序,我们可以看到,任务是通过自定义的taskScheduler进行调度的

总结

下面的流程图展示了Spring Boot创建定时任务的完整流程


Reprint please specify: wbl 从EnableScheduling说起

Previous
Protocol Buffer-1 Protocol Buffer-1
简介Protocol Buffer是Google提供的一种数据序列化协议,官方对protobuf的定义如下 Protocol Buffer是一种轻便高效的结构化数据存储格式,可以洪湖结构化数据序列化,很适合做数据存储或RPC数据交换格式,
2020-03-16
Next
Java8 HashMap Java8 HashMap
HashMap数据结构数组 + 链表HashMap要实现哈希表的效果,尽量实现O(1)级别的增删改查,它的具体实现是同时使用了数组和链表。 首先HashMap的内部定义了静态类Node来实现Entry接口 static class Nod
2020-03-07