spring如何设置定时任务详解(@Scheduled)

以前用过这个注解实现定时任务,但是只是使用,现在做项目又用到了这个功能,系统的学习一下~

spring定时任务设置有两种方式,注解和xml配置。推荐使用注解,在本文章也主要介绍注解方式配置

目录:注解方式配置定时任务->@Scheduled->原理简介->其他

一:注解方式配置定时任务:

下面的步骤默认spring的其他配置项都已经配置好(比如启动注解配置,包路径扫描等)

1:在spring配置文件中配置,添加命名空间

  • xmlns添加:
xmlns:task="http://www.springframework.org/schema/task"
  • xsi:schemaLocation添加
    • 注意"4.3"这是版本号,要修改和你的其他xsd版本号一致
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd"
  • 启动注解驱动
    • 注意“dataScheduler”为自定义名称,可以通过自己的业务定义 合适 的名称
<task:annotation-driven scheduler="dataScheduler"/>
  • 开启任务调度器,并配置线程池大小
    • 注意此处的id指定的就是上面的自定义名称
    • spring的任务调度默认是单线程的,如果你的项目会有多任务定时执行,并且执行时间会相交的话,应该根据任务的具体执行情况配置线程池大小
    • 如果不配置线程池,并且A和B任务在同一时间执行,A先执行的话,B要等待A执行完才可以执行,AB不会同时执行
<task:scheduler id="dataScheduler" pool-size="5"/>

2:使用注解配置定时任务

  • 在你需要配置定时任务的方法上使用注解@Scheduled即可,下面一个简单案例:

    • 注意 下面的案例是在每天的早上2点执行
    • “0 0 2 * * *”是怎么组合的?下面会详细介绍@Scheduled()注解
@Scheduled(cron = "0 0 2 * * ?")
public void init(){
    todo...
}

在此需要注意:@Scheduled只能注释在无参的方法上,我看网上有许多博客说必须无参无返回值的,但是经过我的测试有返回值是可以的,可能是版本更新了吧。

现在就算是完成spring定时器的使用了,下面让我们来详细的看一下@Scheduled注解吧~

二:@Scheduled

@Scheduled注解是Spring专门为定时任务设计的注解

首先,让我们来看看这个注解是怎么组成的吧(适用于版本JDK8与spring4.3及其以上)

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {

    String cron() default "";
    String zone() default "";
    long fixedDelay() default -1L;
    String fixedDelayString() default "";
    long fixedRate() default -1L;
    String fixedRateString() default "";
    long initialDelay() default -1L;
    String initialDelayString() default "";
}

从上述代码中看以看出:

1:@Scheduled被注解部分:

  • 元注解@Target表明@Scheduled注解可以在方法上使用(ElementType.METHOD),也可以作为元注解对其他注解进行注解(ElementType.ANNOTATION_TYPE)
  • 元注解@Retention表明此注解会被JVM所保留,也就是会保存在运行时(RetentionPolicy.RUNTIME)
  • 元注解@Documented表明此注解应该被 javadoc工具记录。默认情况下javadoc是不包括注解的。
  • JDK8添加的注解@Repeatable表明此注解可以在同一个地方被重复使用

上述的所涉及到的注解有不清楚作用的,可以自行baidu\google,网上有好多介绍的文章。

2:@Scheduled参数部分,总共包含8各部分,我们来分别看一下其作用:

  • cron:一个类似cron的表达式,扩展了通常的UN * X定义,包括秒,分,时,星期,月,年的触发器。
  • fixedDelay:在最后一次调用结束和下一次调用开始之间以固定周期(以毫秒为单位)执行带注释的方法。(要等待上次任务完成后)
  • fixedDelayString:同上面作用一样,只是String类型
  • fixedRate:在调用之间以固定的周期(以毫秒为单位)执行带注释的方法。(不需要等待上次任务完成)
  • fixedRateString:同上面作用一样,只是String类型
  • initialDelay:第一次执行fixedRate()或fixedDelay()任务之前延迟的毫秒数 。
  • initialDelayString:同上面作用一样,只是String类型
  • zone:指明解析cron表达式的时区。

cron可以组合出更多的定时情况,fixedDelay和fixedRate只能定义每隔多长时间执行一次。

在上述cron、fixedDelay、fixedRate 只能同时存在一个,使用其中一个就不能使用另外的一个,否则会报错“java.lang.IllegalStateException”

3:cron参数

一个cron表达式可以有6个元素或者7个元素组成(“年”这个元素可以省略,省略之后就是默认“每一年”)

3.1:按顺序依次为:

  1. 秒(0~59)
  2. 分钟(0~59)
  3. 小时(0~23)
  4. 天(0~31)
  5. 月(0~11)
  6. 星期(1~7 )或者( SUN,MON,TUE,WED,THU,FRI,SAT。其中SUN = 1)
  7. 年份(1970-2099)

3.2:每个元素可以接受的值:

字段 允许值 允许的特殊字符
0-59 , - * /
0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
空, 1970-2099 , - * /

3.3:一些特殊字符解释与注意事项,可以结合下面的小案例来理解:

  • 其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。
  • 其中的“日”由于"月份中的日期"和"星期"这两个元素互斥的,必须要对其中一个设置“?”。
  • 有些子表达式能包含一些范围或列表
    • 例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”
  • “*”字符代表所有可能的值
  • “/”字符用来指定数值的增量
    • 例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟
    • 在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样
  • “?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值
    • 当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”
  • “L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写
    • 如果在“L”前有具体的内容,它就具有其他的含义了。例如:“6L”表示这个月的倒数第6天
    • 注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题
  • “W” 字符代表着平日(Mon-Fri),并且仅能用于日域中。它用来指定离指定日的最近的一个平日。大部分的商业处理都是基于工作周的,所以 W 字符可能是非常重要的。
    • 例如,日域中的 15W 意味着 "离该月15号的最近一个平日。" 假如15号是星期六,那么 trigger 会在14号(星期五)触发,因为星期四比星期一离15号更近。
  • “C”:代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。

3.4:一些小案例:

  • "0 0 10,14,16 * * ?" 每天上午10点,下午2点,4点
  • "0 0/30 9-17 * * ?" 朝九晚五工作时间内每半小时
  • "0 0 12 ? * WED" 表示每个星期三中午12点
  • "0 0 12 * * ?" 每天中午12点触发
  • "0 15 10 ? * *" 每天上午10:15触发(这个和下一个案例说明,必须"月份中的日期"和"星期"中有一个设置为“?”)
  • "0 15 10 * * ?" 每天上午10:15触发
  • "0 15 10 * * ? *" 每天上午10:15触发(7个元素类型案例,第七个元素代表年)
  • "0 15 10 * * ? 2005" 2005年的每天上午10:15触发
  • "0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发
  • "0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发
  • "0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
  • "0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发
  • "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
  • "0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
  • "0 15 10 15 * ?" 每月15日上午10:15触发
  • "0 15 10 L * ?" 每月最后一日的上午10:15触发
  • "0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发
  • "0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
  • "0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发

到这个地方你应该对@Scheduled有一个较全面的理解了,下面我们就来简单的看一下其实现原理吧~

三:原理简介

1:主要过程:

  1. spring在使用applicationContext将类全部初始化。

  2. 调用ScheduledAnnotationBeanPostProcessor类中的postProcessAfterInitialization方法获取项目中所有被注解 @Scheduled注解的方法 。

  3. 通过processScheduled方法将所有定时的方法存放在Set<ScheduledTask> tasks = new LinkedHashSet(4); 定时任务队列中,并解析相应的参数。顺序存放,任务也是顺序执行。存放顺序为cron>fixedDelay>fixedRate

  4. 将解析参数后的定时任务存放在一个初始容量为16 的map中,key为bean name,value为定时任务:private final Map<Object, Set<ScheduledTask>> scheduledTasks = new IdentityHashMap(16);

  5. 之后交给ScheduledTaskRegistrar类的方法scheduleTasks去添加定时任务。

2:上述就是一个大致过程,下面看一下相应的源码:

注意 :spring对定时任务的操作的源码全部在spring-context.jar包下的org.springframework.scheduling包下面,主要包含三部分:annotation、config、 support,大家有兴趣的话可以去看看

1:获取项目中所有被注解 @Scheduled注解的方法

public Object postProcessAfterInitialization(Object bean, String beanName) {
    Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
    if (!this.nonAnnotatedClasses.contains(targetClass)) {
        Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, new MetadataLookup<Set<Scheduled>>() {
            public Set<Scheduled> inspect(Method method) {
                //获取注解方法
                **Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);**
                return !scheduledMethods.isEmpty() ? scheduledMethods : null;
            }
        });
        if (annotatedMethods.isEmpty()) {
            ...
        } else {
            Iterator var5 = annotatedMethods.entrySet().iterator();
            while(var5.hasNext()) {

                Entry<Method, Set<Scheduled>> entry = (Entry)var5.next();
                Method method = (Method)entry.getKey();
                Iterator var8 = ((Set)entry.getValue()).iterator();

                while(var8.hasNext()) {
                    Scheduled scheduled = (Scheduled)var8.next();
                    //将获取的任务进行参数解析并存放到任务队列
                    this.processScheduled(scheduled, method, bean);
                }
            }
           ...
        }
    }
    return bean;
}

2:通过processScheduled方法将所有定时的方法存放在定时任务队列中

protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
    try {
        ...
        //解析initialDelayString参数
        String initialDelayString = scheduled.initialDelayString();
        if (StringUtils.hasText(initialDelayString)) {
           ...
        }
        //解析cron参数
        String cron = scheduled.cron();
        if (StringUtils.hasText(cron)) {
            ...
            //存放到任务队列中
            tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
        }
        ...
        //解析fixedDelay参数
        long fixedDelay = scheduled.fixedDelay();
        if (fixedDelay >= 0L) {
            Assert.isTrue(!processedSchedule, errorMessage);
            processedSchedule = true;
            tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
        }
        String fixedDelayString = scheduled.fixedDelayString();
        if (StringUtils.hasText(fixedDelayString)) {
            ...
            //存放到任务队列中
            tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
        }
        //解析fixedRate参数
        long fixedRate = scheduled.fixedRate();
        if (fixedRate >= 0L) {
            Assert.isTrue(!processedSchedule, errorMessage);
            processedSchedule = true;
            tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
        }
        String fixedRateString = scheduled.fixedRateString();
        if (StringUtils.hasText(fixedRateString)) {
            ...
            //存放到任务队列中
            tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
        }
        Assert.isTrue(processedSchedule, errorMessage);
        Map var19 = this.scheduledTasks;
        //并发控制并将任务存放在map中
        synchronized(this.scheduledTasks) {
            Set<ScheduledTask> registeredTasks = (Set)this.scheduledTasks.get(bean);
            if (registeredTasks == null) {
                registeredTasks = new LinkedHashSet(4);
                //将任务存放在map中
                this.scheduledTasks.put(bean, registeredTasks);
            }
            ((Set)registeredTasks).addAll(tasks);
        }
    } catch (IllegalArgumentException var26) {
        throw new IllegalStateException("Encountered invalid @Scheduled method '" + method.getName() + "': " + var26.getMessage());
    }
}

3:之后交给ScheduledTaskRegistrar类的方法scheduleTasks去添加定时任务

protected void scheduleTasks() {
    if (this.taskScheduler == null) {
        this.localExecutor = Executors.newSingleThreadScheduledExecutor();
        this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
    }
    Iterator var1;
    if (this.triggerTasks != null) {
        var1 = this.triggerTasks.iterator();
        while(var1.hasNext()) {
            TriggerTask task = (TriggerTask)var1.next();
            this.addScheduledTask(this.scheduleTriggerTask(task));
        }
    }
    if (this.cronTasks != null) {
        var1 = this.cronTasks.iterator();
        while(var1.hasNext()) {
            CronTask task = (CronTask)var1.next();
            this.addScheduledTask(this.scheduleCronTask(task));
        }
    }
    IntervalTask task;
    if (this.fixedRateTasks != null) {
        var1 = this.fixedRateTasks.iterator();
        while(var1.hasNext()) {
            task = (IntervalTask)var1.next();
            this.addScheduledTask(this.scheduleFixedRateTask(task));
        }
    }
    if (this.fixedDelayTasks != null) {
        var1 = this.fixedDelayTasks.iterator();
        while(var1.hasNext()) {
            task = (IntervalTask)var1.next();
            this.addScheduledTask(this.scheduleFixedDelayTask(task));
        }
    }
}

此部分只是对原理进行了简单的介绍,如果有兴趣深入了解,可以去看看源码~

四:其他

做定时任务还可以使用java自带的原生API,Timer和TimerTask去设计。

  • Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。

  • TimerTask:定义一个被执行的任务,Timer 安排该任务为一次执行或重复执行的任务。

可以这样理解Timer是一种定时器工具,用来在一个后台线程计划执行指定任务,而TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务。

这里就简单的提一下,并不是本文的重点,具体的用法自行google吧~

refer:博客 官网

如果感觉这篇文章对您有所帮助,请点击一下喜欢或者关注博主,您的喜欢和关注将是我前进的最大动力!

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

推荐阅读更多精彩内容

  • 参考springBoot中@Scheduled执行原理解析 spring在初始化bean后,通过“postProc...
    huoyl0410阅读 768评论 1 0
  • 一点知识 在JAVA开发领域,目前可以通过以下几种方式进行定时任务: Timer:jdk中自带的一个定时调度类,可...
    雅倩兰爸爸阅读 3,827评论 1 28
  • 1.Scheduled定时任务器2.整合Quartz定时任务框架3.cron表达式 1.Scheduled定时任务...
    Yanl__阅读 455评论 0 0
  • 系统运行中常常会遇到定时任务或延时任务,SpringBoot中提供了@Scheduled注解对定时或延时任务的声明...
    yangsg阅读 626评论 0 2
  • 以前片面的认为管理好时间轴,不浪费时间,就是做好了时间管理,今天的课程让我认识到关于时间的分类,时间的优先级排...
    慢慢有话说阅读 557评论 3 2