当前位置: 首页 > 科技观察

SpringBoot定时任务与Cron表达式详解

时间:2023-03-17 20:24:07 科技观察

摘要:讲解如何使用SpringBoot定时任务,通过源码讲解如何使用多线程处理各个定时任务。详细描述了cron表达式的使用。一、定时任务概述定时任务在后台项目开发中经常用到,定时任务的实现方式多种多样。以下是一些常见的定时任务实现方式:1.Quartz:Quartz应用广泛,是一个强大的调度器,当然使用起来相对麻烦一些;2、java.util包中的Timer,它也可以实现定时任务,但是功能太单一,所以很少用到。3、今天要介绍的就是Spring自带的定时任务Schedule。其实也算是一个简化版,一个轻量级的Quartz,使用起来也比较方便。二、实现定时任务1、创建定时任务importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.scheduling.annotation.Scheduled;importorg.springframework.stereotype.Component;/***说明:构建并执行定时任务*设计者:jack*日期:2017/8/10*版本:1.0.0*/@ComponentpublicclassScheduledTasks{privateLoggerlogger=LoggerFactory.getLogger(ScheduledTasks.class);privateintfixedDelayCount=1;privateintfixedRateCount=1;privateinitialDelayCount=1;privateintinitialDelayCount=1;privateintc@Scheduled(fixedDelay=5000)//fixedDelay=5000表示当前方法执行5000ms后,Springscheduling会再次调用该方法publicvoidtestFixDelay(){logger.info("===fixedDelay:{}执行方法",fixedDelayCount++);}@Scheduled(fixedRate=5000)//fixedRate=5000表示Springscheduling会在当前方法开始执行5000ms后再次调用该方法。fixedRateCount++);}@Scheduled(initialDelay=1000,fixedRate=5000)//initialDelay=1000表示延迟第一个任务执行1000mspublicvoidtestInitialDelay(){logger.info("===initialDelay:{}thexecution方法",initialDelayCount++);}@Scheduled(cron="00/1***?")//cron接受cron表达式,根据cron表达式确定定时规则publicvoidtestCron(){logger.info("===initialDelay:{}thexecutionmethod",cronCount++);}}我们使用@Scheduled来创建定时任务。该注解用于标记定时任务方法。通过查看@Scheduled的源码可以看出它支持多个参数:(1)cron:cron表达式,指定在特定时间执行的任务;(2)fixedDelay:表示距离上一次任务执行完成后多久,参数类型为long,单位为ms;(3)fixedDelayString:含义同fixedDelay,只是参数类型变成了String;(4)fixedRate:表示任务以一定的频率执行,参数类型为long,单位为ms;(5)fixedRateString:含义同fixedRate,只是参数类型改为String;(6)initialDelay:表示延迟多长时间再执行任务,参数类型为long,单位为ms;(7)initialDelayString:含义同initialDelay,只是参数类型改为String;(8)zone:时区,默认为当前时区,一般没用到。2.打开定时任务importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.scheduling.annotation.EnableScheduling;/***说明:启动类*Designer:jack*Date:2017/8/10*Version:1.0.0*/@SpringBootApplication@EnableSchedulingpublicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.run(Application.class,args);}}注意这里的@EnableScheduling注解,它的作用是发现注解@Scheduled任务由后台执行。没有它,计划任务无法执行。引用官方文档原文:@EnableScheduling确保创建后台任务执行器。没有它,什么都无法安排。3、执行结果(单线程)至此我们完成了一个简单的定时任务模型。接下来执行springBoot,观察执行结果。2017-08-1112:06:19.738INFO52252---[pool-1-thread-1]com.test.ScheduledTasks:===initialDelay:第一个执行方法2017-08-1112:06:23.739INFO52252--[pool-1-thread-1]com.test.ScheduledTasks:===fixedRate:第二种执行方式2017-08-1112:06:23.739INFO52252---[pool-1-thread-1]com.test.ScheduledTasks:===fixedDelay:第二次执行方式2017-08-1112:06:24.738INFO52252---[pool-1-thread-1]com.test.ScheduledTasks:===initialDelay:第二次执行method2017-08-1112:06:28.739INFO52252---[pool-1-thread-1]com.test.ScheduledTasks:===fixedRate:第三种执行方法2017-08-1112:06:28.740INFO52252---[pool-1-thread-1]com.test.ScheduledTasks:===fixedDelay:第三种执行方式2017-08-1112:06:29.739INFO52252---[pool-1-thread-1]com.test.ScheduledTasks:===initialDelay:第三种执行方式2017-08-1112:06:33.735INFO52252---[pool-1-thread-1]com.test.ScheduledTasks:===fixedRate:第4种执行方式2017-08-1112:06:33.741INFO52252---[pool-1-thread-1]com.test.ScheduledTasks:===fixedDelay:第4种执行方法2017-08-1112:06:34.738INFO52252---[pool-1-thread-1]com.test.ScheduledTasks:===initialDelay:4th第二种执行方式从控制台输入的结果我们可以看到,所有的定时任务都是在同一个线程池中用同一个线程处理的,那么我们如何并发处理每一个定时任务呢?请继续往下看4.定时任务的多线程处理看到控制台输出结果,所有的定时任务都是通过一个线程处理的,我猜定时任务的配置里面设置了一个SingleThreadScheduledExecutor,所以看了源码,从ScheduledAnnotationBeanPostProcessor类开始一路往下看。果然,在ScheduledTaskRegistrar(定时任务注册类)中的ScheduleTasks中还有一个判断:}这意味着如果taskScheduler为空,则为定时任务创建单线程线程池。该类中还有一个设置taskScheduler的方法:this.taskScheduler=newConcurrentTaskScheduler((((ScheduledExecutorService)scheduler));}else{(thrownewIllegalArgumentException:"+schedul));}}这个问题很简单,我们只需要通过调用这个方法显式设置一个ScheduledExecutorService就可以实现并发效应。我们要做的就是实现SchedulingConfigurer接口,重写configureTasks方法;importorg.springframework.context.annotation.Configuration;importorg.springframework.scheduling.annotation.SchedulingConfigurer;importorg.springframework.scheduling.config.ScheduledTaskRegistrar;importjava。util.concurrent.Executors;/***说明:多线程执行定时任务*Designer:jack*Date:2017/8/10*Version:1.0.0*/@Configuration//所有定时任务放在一个线程池中,定时任务启动时使用不同的线程。publicclassScheduleConfigimplementsSchedulingConfigurer{@OverridepublicvoidconfigureTasks(ScheduledTaskRegistrartaskRegistrar){//设置一个定时任务的线程池,长度为10taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));}}注:一开始我尝试在ScheduledAnnotationBeanPostProcessor中发一篇文章,因为那里也是ScheduledAnnotationBeanPostProcessor类中的一个类似的方法setScheduler(),但是***失败了。不知道这个类能不能用来实现定时任务的并发。如果实现了,请把方法告诉大家。5.执行结果(并发)2017-08-1112:21:16.000INFO52284---[pool-1-thread-3]com.test.ScheduledTasks:===initialDelay:第一个执行方法2017-08-1112:21:19.998INFO52284---[pool-1-thread-4]com.test.ScheduledTasks:===fixedRate:第二种执行方式2017-08-1112:21:19.998INFO52284---[pool-1-thread-1]com.test.ScheduledTasks:===fixedDelay:第二种执行方式2017-08-1112:21:20.999INFO52284---[pool-1-thread-4]com.test.ScheduledTasks:===initialDelay:第二种执行方式2017-08-1112:21:25.000INFO52284---[pool-1-thread-2]com.test.ScheduledTasks:===fixedRate:第三种执行方式2017-08-1112:21:25.000INFO52284---[pool-1-thread-6]com.test.ScheduledTasks:===fixedDelay:第三种执行方式2017-08-1112:21:25.997INFO52284---[pool-1-thread-3]com.test.ScheduledTasks:===initialDelay:第三种执行方式2017-08-1112:21:30.000INFO52284---[pool-1-thread-7]com.test.ScheduledTasks:===fixedRate:第四种执行方法2017-08-1112:21:30.000INFO52284---[pool-1-thread-8]com.test.ScheduledTasks:===fixedDelay:第四种执行方式2017-08-1112:21:31.000INFO52284---[pool-1-thread-7]com.test.ScheduledTasks:===初始化ialDelay:第四种执行方式的结果,通过控制台输出可以看出每个定时任务由不同的线程处理。三、cron详解1、cron表达式的定义。由6或7个独立的域组成,每个域对应一个含义(秒、分、时、月、日、月、周、年),其中年份是一个可选字段。不过这里敲黑板,spring的schedule值支持6域的表达,也就是不能设置年份,超过6就会报错。源码如下:/***Parsethegivenpatternexpression.*/privatevoidparse(Stringexpression)throwsIllegalArgumentException{String[]fields=StringUtils.tokenizeToStringArray(expression,"");if(!areValidCronFields(fields)){thrownewIllegalArgumentException(String.format("Cronexpressionmustfieldsconsistof6found%din\"%s\")",fields.length,expression));}setNumberHits(this.seconds,fields[0],0,60);setNumberHits(this.minutes,fields[1],0,60);setNumberHits(this.hours,fields[2],0,24);setDaysOfMonth(this.daysOfMonth,fields[3]);setMonths(this.months,fields[4]);setDays(this.daysOfWeek,replaceOrdinals(fields[5],"SUN,MON,TUE,WED,THU,FRI,SAT"),8);if(this.daysOfWeek.get(7)){//Sunday可以表示为0or7this.daysOfWeek.set(0);this.daysOfWeek.clear(7);}}privatestaticbooleanareValidCronFields(String[]fields){return(fields!=null&&fields.length==6);}2。各字段中可以出现的字符类型及各字符的含义(一)各字段支持的字符类型二:可以出现“,-*/”四个字符,有效范围为0-59分钟的整数:可以出现",-*/"四个字符,有效范围为0-59整数:可以出现",-*/"四个字符,有效范围为0-23整数月:可以出现",-*/?LWC"八个字符,有效范围为0-31整数月:可以出现",-*/"四个字符,有效范围为1-12整数或JAN-DEc星期:可以出现",-*/?LC#”四个字符,有效范围为1-7整数或SUN-SAT两个范围1表示星期日,2表示星期一,以此类推以秒为单位*,表示每秒触发一次事件。;?:可以只能用在月的天和星期的字段中。表示不指定值。当两个子表达式之一指定一个值时,为了避免冲突,需要设置另一个子表达式的值改为“?”;-:表示范围,比如在域中使用5-20,表示从5分钟到20分钟每分钟触发一次/:表示开始时间触发,然后再触发每隔固定时间触发一次,例如在域中使用5/20,表示5分、25分、45分,分别触发一次。,:表示列出枚举值。例如:在域中使用5、20,表示每5分钟和20分钟触发一次L:表示***,只能出现在星期和月份的字段中,如果用1Linweek字段,表示在***字段中,星期日触发。W:表示有效的工作日(周一到周五),只能出现在月份的哪一天字段中,系统会在离指定日期最近的有效工作日触发事件。注意W的最近搜索它不会跨月LW:这两个字符可以一起使用,表示某个月的第一个工作日,即第一个星期五。#:用于确定每个月的星期几,只出现在该月的第几天字段中。例如1#3,表示某月的第三个星期日。(3)表达式示例参考spring官方注释:*

示例模式:*

    *
  • "00****"=topofeveryhourofeveryday.
  • *
  • "*/10*****"=每十秒。
  • *
  • "008-10***"=每天的8、9和10点。
  • li>*
  • "00/308-10***"=每天8:00、8:30、9:00、9:30和10点。
  • *
  • "009-17**MON-FRI"=在工作日的九点到五点
  • *
  • "0002512?"=每个圣诞节午夜
  • "00****"表示每小时0分0秒执行一次"*/10*****"表示每10秒执行一次"008-10***"表示每天8、9、10点执行"00/308-10***"表示每天8:00-10:00每半小时执行"009-17**MON-FRI”表示0从周一到周五的9:00到17:00执行“0002512?”分0秒,表示每年圣诞节(12月25日)0:00:00执行