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<ContextRefreshedEvent>, 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<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
                (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
                    Set<Scheduled> 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方法用来计算下一个时间点的(本人看的有点蒙圈)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容