SpringBoot定时任务

  • 源码基于Spring5.0.5版本
  • 在SpringBoot项目中只需要添加@EnableScheduling即可开启定时任务,在方法添加@Scheduled注解即可实现固定周期某些方法.
  • @EnableScheduling注解中引入了SchedulingConfiguration配置,而该配置注册了一个名为ScheduledAnnotationBeanPostProcessor的bean
  • 看到BeanPostProcessor就大致知道该类用于Bean实例化或初始化前后的处理类,以下是该类实现的接口
//核心实现接口MergedBeanDefinitionPostProcessor
public class ScheduledAnnotationBeanPostProcessor
        implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
        Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
        SmartInitializingSingleton, ApplicationListener, DisposableBean {
}

该类主要处理业务的方法是postProcessAfterInitialization,finishRegistration,processScheduled

postProcessAfterInitialization

public Object postProcessAfterInitialization(final Object bean, String beanName) {
    Class targetClass = AopProxyUtils.ultimateTargetClass(bean);
       
    if (!this.nonAnnotatedClasses.contains(targetClass)) {
                //查找类中包含Scheduled或Schedules注解的方法
        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()) {
            this.nonAnnotatedClasses.add(targetClass);
            if (logger.isTraceEnabled()) {
                logger.trace("No @Scheduled annotations found on bean class: " + bean.getClass());
            }
        }
        else {
            // Non-empty set of methods
      //将包含@Scheduled或@Schedules注解的方法按类型包装成Task并交由ScheduledTaskRegistrar处理
            annotatedMethods.forEach((method, scheduledMethods) ->
                    scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
            if (logger.isDebugEnabled()) {
                logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
                        "': " + annotatedMethods);
            }
        }
    }
    return bean;
}

processScheduled

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);
    }
    if (StringUtils.hasLength(cron)) {
        Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
        processedSchedule = true;
        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))));
    }
}

  • 上述方法,通过反射将Bean中包含指定注解的方法找出来,并将调用过程包装为Task的子类交由ScheduledTaskRegistrar 处理
  • Schedules该注解是个复合注解,一个方法上需要由多种定时调用可以使用该注解,当然如果jdk版本为1.8可以不使用该注解,直接在一个方法上添加多个Scheduled注解即可
  • 代码摘抄了cron表达式包装成CronTask的过程
  • Trigger接口主要是用来计算定时任务下次要执行的时间
  • CronTrigger类做了以下事
    • 将cron表达式交由CronSequenceGenerator解析
    • 下次定时任务要执行的时间也交由CronSequenceGenerator#next来计算获取
  • 真正执行方法调用逻辑是由TaskScheduler接口来处理,默认实现ConcurrentTaskScheduler 内部包含一个ScheduledThreadPoolExecutor线程池(核心线程1个).通过换算出来的下次执行时间及封装好的ScheduledMethodRunnable 即可完成完整的定时调用

题外话

  • @Scheduled#fixedDelay 固定时间执行某个方法(方法完成到下次开始执行时间固定),具体执行在ScheduledTaskRegistrar#scheduleFixedDelayTask方法
  • @Scheduled#fixedRate 固定周期执行某个方法(方法执行开始时间到下次方法执行开始时间固定),具体执行方法在ScheduledTaskRegistrar#scheduleFixedRateTask方法
  • 如果不想用注解形式实现定时任务可以通过实现SchedulingConfigurer接口对ScheduledTaskRegistrar#addTriggerTask,ScheduledTaskRegistrar#addCronTask,ScheduledTaskRegistrar#addFixedRateTask,ScheduledTaskRegistrar#addFixedDelayTask.同时该接口支持手动创建线程池等信息
  • CronSequenceGenerator类主要用于解析cron表达式,根据给定的时间推算出下次的时间,内部采用BitSet占位方式将所有要执行的时间点标示出来.#next方法用来计算下一个时间点的(本人看的有点蒙圈)

你可能感兴趣的:(SpringBoot定时任务)