【定时任务】——Spring定时任务Scheduled

       定时任务在日常开发过程中非常常见,而且在日常的项目开发中也有多种实现方式,而且做任务调度的框架有很多种,小编最近的感受,如果想真正使用好任务调度还是存在困难的,所以分步学习,逐个击破!在这篇文章小编主要写spring中Scheduled。

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

注解方式配置定时任务

下面的步骤默认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只能注释在无参的方法上,我看网上有许多博客说必须无参无返回值的,但是经过我的测试有返回值是可以的,可能是版本更新了吧。

@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”

原理简介

1:主要过程:

  1. spring在使用applicationContext将类全部初始化。
  2. 调用ScheduledAnnotationBeanPostProcessor类中的postProcessAfterInitialization方法获取项目中所有被注解 @Scheduled注解的方法 。
  3. 通过processScheduled方法将所有定时的方法存放在Set tasks = new LinkedHashSet(4); 定时任务队列中,并解析相应的参数。顺序存放,任务也是顺序执行。存放顺序为cron>fixedDelay>fixedRate
  4. 将解析参数后的定时任务存放在一个初始容量为16 的map中,key为bean name,value为定时任务:private final Map<Object, Set> 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吧~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Scheduled定时任务是一种在Spring Boot中创建定时任务的方式。目前主要有三种创建方式: 1. 基于注解(@Scheduled)的静态任务:通过在方法上添加@Scheduled注解来指定任务的执行时间。 2. 基于接口(SchedulingConfigurer)的动态任务:通过实现SchedulingConfigurer接口,可以根据数据库的内容动态调度任务。 3. 基于注解的多线程定时任务:通过使用@Scheduled注解和多线程来实现定时任务的并发执行。 在使用Spring Scheduled定时任务时,需要在启动类上添加@EnableScheduling注解来开启定时任务功能。然后可以在方法上使用@Scheduled注解来指定任务的执行时间,或者实现SchedulingConfigurer接口来添加定时任务。同时,可以配置定时任务的多线程非阻塞运行,以提高任务的并发性能。 以上是关于Spring Scheduled定时任务的简要介绍和使用方式。如果需要更详细的信息,可以参考引用\[1\]和引用\[2\]中的内容。 #### 引用[.reference_title] - *1* [SpringBoot之Scheduled定时任务详解](https://blog.csdn.net/weixin_41003771/article/details/102655202)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [spring schedule定时任务详解](https://blog.csdn.net/qq_34480904/article/details/122410711)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mandy_i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值