SpringBoot 07 如何实现任务调度Scheduled及常见问题


定时调度是指在无人值守的时候,系统可以在某一时刻执行某些特定的功能而采用的一种机制。本文总结三种实现任务调度的方法


1.使用@Scheduled 实现调度

本方法主要通过@EnableScheduling和 @Scheduled两个注解实现调度, 两个注解并不是springboot增加的注解,而是springframwork的注解,由spring-context包提供

实现方式如下:

  • 启动主类不需要做其他特殊处理直接使用@SpringBootApplication即可,并不需要其他注解
  • 调度类ScheduledTaskService 首先是一个bean,然后在方法上使用@EnableScheduling开启任务调度
  • 使用@Scheduled标注配置调度任务方法,其中支持三个属性fixedRate  、fixedDelay 、cron 
@SpringBootApplication
public class SchedulerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SchedulerApplication.class, args);
    }
}

1.fixedRate是按照一定的速率执行

       此调度是从上一次方法执行开始的时间算起,如果上一次方法阻塞住了,下一次也是不会执行,但是在阻塞这段时间内累计应该执行的次数,当不再阻塞时,一下子把这些全部执行掉,而后再按照固定速率继续执行

@Component
@EnableScheduling
public class ScheduledTaskService {

    private static final SimpleDateFormat dataFormat = new SimpleDateFormat("HH:mm:ss");

    @Scheduled(fixedRate = 5000)
    //@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)  //使用timeUnit设置时间单位
    public void reportCurrentTime() throws InterruptedException {
        System.out.println("间隔5秒执行:" + dataFormat.format(new Date()));
        Thread.sleep(6000);
        System.out.println("间隔5秒结束:" + dataFormat.format(new Date()));
    }
}
启动SpringBoot查看执行结果:可见先前的任务结束后不会再等间隔

间隔5秒执行:19:55:00
间隔5秒结束:19:55:06
间隔5秒执行:19:55:06
间隔5秒结束:19:55:12
间隔5秒执行:19:55:12
间隔5秒结束:19:55:18

2.fixedDelay控制方法执行的间隔时间

      此调度方式是以上一次方法执行完开始算起,如上一次方法执行阻塞住了,那么直到上一次执行完,并间隔给定的时间后,执行下一次

@Component
@EnableScheduling
public class ScheduledTaskService {

    private static final SimpleDateFormat dataFormat = new SimpleDateFormat("HH:mm:ss");


    //@Scheduled(fixedDelay = 5000)
    @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)  //可以使用timeUnit设置时间单位
    public void reportCurrentTime2() throws InterruptedException {
        System.out.println("间隔5秒执行:" + dataFormat.format(new Date()));
        Thread.sleep(6000);
        System.out.println("间隔5秒结束:" + dataFormat.format(new Date()));
    }
}
启动SpringBoot查看执行结果:上一次执行5秒后才会执行

间隔5秒执行:20:00:55
间隔5秒结束:20:01:01

间隔5秒执行:20:01:06
间隔5秒结束:20:01:12

间隔5秒执行:20:01:17
间隔5秒结束:20:01:23

3. 使用cron表达式定制任务

执行的方式是与fixedDelay相近的,也是会按照上一次方法结束时间开始算起, 调度代码如下

@Component
@EnableScheduling
public class ScheduledTaskService {

    @Scheduled(cron = "0/3 * * * * ?")
    public void fixTimeExecution() {
        System.out.println("指定时间执行:" + dataFormat.format(new Date()));
    }

}
启动SpringBoot查看执行结果:执行间隔是每3秒打印一次

指定时间执行:20:06:30
指定时间执行:20:06:33
指定时间执行:20:06:36
指定时间执行:20:06:39
指定时间执行:20:06:42
指定时间执行:20:06:45

其中 cron表达式,如* * * * * *由六个空格分隔的时间和日期字段组成,每个字段都有自己的有效值范围:

 ┌───────────── second (0-59)
 │ ┌───────────── minute (0 - 59)
 │ │ ┌───────────── hour (0 - 23)
 │ │ │ ┌───────────── day of the month (1 - 31)
 │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
 │ │ │ │ │ ┌───────────── day of the week (0 - 7)
 │ │ │ │ │ │          (0 or 7 is Sunday, or MON-SUN)
 │ │ │ │ │ │
 * * * * * *
  • @Scheduled cron只支持6位,分别是:秒、分、时、日、月、周
  • @Scheduled cron不支持第7位“年”,即不支持配置这样的格式,例如“0/1 * * * * ? 2099” 
  • @Scheduled(cron = "-")   当配置为“-” 时,表示不启动不启动任务,从corn的属性标注可以看到此信息
  • @Scheduled(cron = "${myjob.cron}")  方式从配置文件中获取表达式配置,从corn的属性标注可以看到此信息
 * <p>The special value {@link #CRON_DISABLED "-"} indicates a disabled cron
	 * trigger, primarily meant for externally specified values resolved by a
	 * <code>${...}</code> placeholder.
	 * @return an expression that can be parsed to a cron schedule
	 * @see org.springframework.scheduling.support.CronExpression#parse(String)
	 */
	String cron() default "";

2.使用XML 实现Scheduler任务

实现方式如下:

  • 使用在XML文件中定义JobDetailFactoryBean等信息,
  • 使用@ImportResource注解将XML配置的Bean加载到容器中,

      一般低版本的Spring项目中调度都配置到XML中,所以当将spring项目迁移成springboot项目的时候使用此方法是比较省事比较快捷的方式,

@ImportResource(locations= {"classpath:spring-job.xml"})
@SpringBootApplication
public class SchedulerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SchedulerApplication.class, args);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
	http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context.xsd" default-lazy-init="false">

    <bean id="autoCancelProduct" class="com.boot.basic.scheduled.AutoCancelProduct">

    </bean>


    <bean id="autoCancelProductDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="name" value="autoCancelProductDetail"/>
        <property name="group" value="Order_Trigger_Group"/>
        <property name="targetObject" ref="autoCancelProduct"/>
        <property name="targetMethod" value="executeJob"/>
        <property name="concurrent" value="false"/>
    </bean>

    <bean id="autoCancelProductTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="name" value="autoCancelProductTrigger"/>
        <property name="group" value="Order_Trigger_Group"/>
        <property name="jobDetail" ref="autoCancelProductDetail"/>
        <property name="cronExpression" value="* 0/2 * * * ? 2099"/>
    </bean>

    <bean id="cancelSchedue" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="autoCancelProductTrigger"/>
            </list>
        </property>
    </bean>

</beans>

3.使用@Configuration实现Scheduler

实现方式

  • Spring3.0 通过定义@Configuration来替换xml  所以job, jobdetail,jobtrigger,schedule等四个对象都可以通过java类实现定义,参考源码如下
@Configuration
public class AutoOrderProductConfig {

    @Bean("autoOrderPorduct")
    public AutoOrderProduct getAutoOrderProduct() {
        return new AutoOrderProduct();
    }

    @Bean("jobDetail")
    public MethodInvokingJobDetailFactoryBean getMethodInvokingJobDetailFactoryBean(AutoOrderProduct autoOrderProduct) {
        MethodInvokingJobDetailFactoryBean factoryBean = new MethodInvokingJobDetailFactoryBean();
        factoryBean.setTargetObject(autoOrderProduct);
        factoryBean.setTargetMethod("executeJob");
        factoryBean.setConcurrent(false);
        return factoryBean;
    }

    @Bean("jobTrigger")
    public CronTriggerFactoryBean cronTriggerFactoryBean() {
        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
        cronTriggerFactoryBean.setName("jobTrigger");
        cronTriggerFactoryBean.setGroup("Order_Trigger_Group");
        cronTriggerFactoryBean.setCronExpression("0/2 * * * * ?");
        cronTriggerFactoryBean.setJobDetail(SpringUtil.getBean("jobDetail"));
        return cronTriggerFactoryBean;
    }

    @Bean("myScheduler")
    public SchedulerFactoryBean getSchedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setTriggers((CronTriggerImpl)SpringUtil.getBean("jobTrigger"));
        return schedulerFactoryBean;
    }

}


4.参考测试代码

源码地址:springboot/basic at master · PNZBEIJINGL/springboot · GitHub


5.常见问题 

1.CronTriggerImpl cannot be cast to class [Lorg.quartz.Trigger

一般是由于范型转换引起的,详细查看下面文章

链接:ClassCastException: XXX are in unnamed module of loader ‘app‘异常分析

2.@Scheduled注解不起作用

  • spring的注解@Scheduled 需要写在实现方法上任务方法不能有返回值(如果有返回值,spring初始化的时候会告诉你有个错误、需要设定一个proxytargetclass的某个值为true),不能指向任何的参数;
  • 如果该方法需要与应用程序上下文的其他对象进行交互,通常是通过依赖注入来实现;
  • 实现类上要有组件的注解@Component。

3.@Scheduled配置cron报错IllegalStateException

Caused by: java.lang.IllegalStateException: Encountered invalid @Scheduled method 'doexec': Cron expression must consist of 6 fields (found 7 in "1 0 * * * ? 2022")
	at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.processScheduled(ScheduledAnnotationBeanPostProcessor.java:462)
	at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.postProcessAfterInitialization(ScheduledAnnotationBeanPostProcessor.java:332)

从报错信息看@Scheduled初始化的时候表达式1 0 * * * ? 2022不正确, 原因是@Scheduled中cron配置只支持6位, 不支持第7位年配置, 所以不能限制某年调度。如果想暂时不适用调度可以配置“-”

   

   上一篇:SpringBoot 06 集成Nacos配置中心以及常见问题

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

=PNZ=BeijingL

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

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

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

打赏作者

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

抵扣说明:

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

余额充值