暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SpringBoot实现定时任务

啃饼思录 2022-06-15
785

写在前面

在实际工作中,定时任务是一个很常见的功能,如定时统计订单数、数据库备份、定时发送短信和邮件、定时统计博客访客等等,简单的定时任务可以直接通过Spring提供的@Scheduled
注解来实现,复杂一点的定时任务则可以通过集成Quartz
([kwɔːts])来实现,本篇将分别介绍如何使用这两种方式实现定时任务。

项目初始化

新建一个名为time-task
的SpringBoot项目,后续将在该项目中进行定时任务的实现。

@Scheduled
注解方式

小试牛刀

「第一步,添加依赖。」在项目的POM文件中新增Web依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

「第二步,开启定时任务。」在项目启动类上添加@EnableScheduling
注解,相应的代码为:

@EnableScheduling
@SpringBootApplication
public class TimeTaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(TimeTaskApplication.class, args);
    }
}

「第三步,配置定时任务。」定时任务主要是通过@Scheduled
注解来进行配置。新建一个component包,并在其中创建一个FirstSchedule
类,其中的代码为:

@Component
public class FirstSchedule {
    @Scheduled(fixedDelay = 1000)
    public void fixedDelay(){
        System.out.println("fixedDelay:"+new Date());
    }
    @Scheduled(fixedRate = 2000)
    public void fixedRate(){
        System.out.println("fixedRate:"+new Date());
    }
    @Scheduled(initialDelay = 1000,fixedRate = 2000)
    public void initialDelay(){
        System.out.println("initialDelay:"+new Date());
    }
    @Scheduled(cron = "0 * * * * ?")
    public void cron(){
        System.out.println("cron:"+new Date());
    }
}

简单解释一下上述代码的含义:

  • 通过@Scheduled
    注解来标注一个定时任务,其中fixedDelay=1000
    表示在当前任务执行「结束」1秒后开启另一个任务;fixedRate=2000
    表示在当前任务执行2秒后开启另一个定时任务;initialDelay=1000
    则表示首次执行的延迟时间为1秒,即任务首次启动任务的延迟时间。
  • @Scheduled
    注解中也可以使用cron表达式,cron="0 * * * * ?"
    表示该定时任务每分钟执行一次。
  • 以上注解值的单位默认为TimeUnit.MILLISECONDS
    ,即毫秒。

配置完成后,接下来启动SpringBoot项目,定时任务部分打印日志如下所示:

cron表达式

cron表达式由6个部分组成,从左到右分别是:秒(second)、分(minute)、时(hour)、日(day of month)、月(month)、周(day of week):

    ┌───────────── 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)
    │ │ │ │ │ │
    │ │ │ │ │ │
    * * * * * *

各个部分的取值情况如下表所示:

部分是否为必填项允许填写的值允许的通配符
秒(second)0-59- *
分(minute)0-59- *
时(hour)0-23- *
日(day of month)1-31- * ? L W
月(month)1-12 or JAN-DEC- *
周(day of week)0-7 or SUN-SAT- * ? L #

解释一下上述通配符的含义:

(1)?
表示不指定值,如果开发者不关心某个部分的取值时,就可以使用它。请注意,月份中的日期和星期可能会起冲突,因此在配置时这两个必须有一个值是?

(2)*
表示所有值,举个例子,当你在“秒”上设置*
时,则表示每秒都会触发;

(3),
用于分开多个值,举个例子,当你在“周”上设置 "MON,WED,FRI",分别表示周一,周三和周五触发;

(4)-
表示区间,举个例子,当你在“秒”上设置 "10-12",则表示10,11,12秒都会触发;

(5)/
用于递增触发,举个例子,当你在“秒”上面设置"5/15",则表示从5秒开始,每增15秒触发(5,20,35,50);

(6)#
表示序号(即每月的第几个周几),举个例子,当你在“周”上设置"6#3",则表示在每月的第三个周六,这种可以用在母亲节和父亲节之类的动态变化节日上面。请注意,在“周”这一部分中,英文字母是不分大小写的,即MON与mon是一样的;

(7)L
表示最后。用在“日”上,表示当月的最后一天(依据当前月份,如果是二月系统会自动判断是否是润年)。在“周”字段上表示星期六,相当于"7"或"SAT"(周日是第一天,即每个星期的开始)。如果在"L"前加上数字,则表示该数据的最后一个。举个例子,当你在“周”上设置"6L",则表示"本月最后一个星期五";

(8)W
表示离指定日期最近的工作日(周一至周五)。举个例子,如果你在“日”上设置"15W",则表示离每月15号最近的那个工作日触发。如果15号是周六,那么找最近的周五(14号)触发,如果15号是周未,则找最近的下周一(16号)触发,如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 "1W",它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,"W"前只能设置具体的数字,不允许区间"-")

(9)L
W
可以组合使用。举个例子,如果在“日”上设置"LW",则表示在本月的最后一个工作日触发,一般用于发薪日。

一些常用的例子

(1)0 0 * * * *
,表示每天每个小时的开始,即0分0秒; 

(2)*/10 * * * * *
,表示每10秒钟; 

(3)0 0 8-10 * * *
,表示每天8点、9点和10点; 

(4)0 0 6,19 * * *
,表示每天早上 6:00 和晚上 7:00;

(5)0 0/30 8-10 * * *
,表示每天 8:00、8:30、9:00、9:30、10:00 和 10:30; 

(6)0 0 9-17 * * MON-FRI
,表示工作日朝九晚五; 

(7)0 0 0 25 12 ?
,表示每个圣诞节的午夜;

(8)0 0 0 L * *
,表示每月最后一天的午夜;

(9)0 0 0 L-3 * *
,表示每月倒数第三天午夜;

(10)0 0 0 1W * *
,表示每月第一个工作日的午夜;

(11)0 0 0 LW * *
,表示每月最后一个工作日的午夜;

(12)0 0 0 * * 5L
,表示每月最后一个星期五午夜; 

(13)0 0 0 * * THUL
,表示每月最后一个星期四的午夜;

(14)0 0 0 ? * 5#2
,表示每月第二个星期五午夜;

(15)0 0 0 ? * MON#1
,表示每月第一个星期一的午夜。

Quartz 方式

Quartz 简介

Quartz是一个功能丰富的开源作业调度库,它由Java编写,可以集成在任何Java应用程序中。开发者可以使用Quartz来创建简单或者复杂的执行计划,它支持数据库、集群、插件以及邮件,且支持cron表达式,具有极高的灵活性。

一般来说,除非定时任务业务非常简单,否则一般是不会使用@Scheduled
注解方式,而是使用Quartz框架。

小试牛刀

「第一步,添加依赖。」在项目的POM文件中新增Quartz依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

「第二步,开启定时任务。」在项目启动类上添加@EnableScheduling
注解,相应的代码为:

@EnableScheduling
@SpringBootApplication
public class TimeTaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(TimeTaskApplication.class, args);
    }
}

「第三步,配置定时任务。」Quartz有三个比较重要的概念,其中JobDetail是用户要做的事情;Trigger是触发器,即事情什么时候做;SchedulerFactory是调度工厂,里面包含多个触发器。

我们先要确定用户要做的事情(JobDetail),由于用户要做的可能不是一件事,因此需要先定义每件事情(Job)。定义Job有两种方式,可以直接定义Bean或者继承QuartzJobBean这一方式。

新建一个component包,如果我们选择“直接定义Bean”这一方式,那么在其中创建一个FirstJob
类,其中的代码为:

@Component
public class FirstJob {
    public void first(){
        System.out.println("FirstJob--->first:"+new Date());
    }
}

这种方式比较简单,直接将这个Bean注册到Spring容器中,但是也有缺点,无法传递参数。如果开发者需要传递参数,那么可以选择“继承QuartzJobBean”这一方式,在其中创建一个SecondJob
类,其中的代码为:

public class SecondJob extends QuartzJobBean {
    private String name;

    public void setName(String name){
        this.name = name;
    }

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        System.out.println("SecondJob--->name:"+new Date());
    }
}

接下来新建一个config包,并在其中创建QuartzConfig
类用于对JobDetail
Trigger
进行配置,相应的代码为:

@Configuration
public class QuartzConfig {
    @Bean
    MethodInvokingJobDetailFactoryBean jobDetailOne(){
        MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
        bean.setTargetBeanName("firstJob");
        bean.setTargetMethod("first");
        return bean;
    }

    @Bean
    JobDetailFactoryBean jobDetailTwo(){
        JobDetailFactoryBean bean = new JobDetailFactoryBean();
        bean.setJobClass(SecondJob.class);
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("name","kenbings");
        bean.setJobDataMap(jobDataMap);
        bean.setDurability(true);
        return bean;
    }

    @Bean
    SimpleTriggerFactoryBean simpleTrigger(){
        SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
        bean.setJobDetail(jobDetailOne().getObject());
        bean.setRepeatCount(3);
        bean.setStartDelay(1000);
        bean.setRepeatInterval(2000);
        return bean;
    }

    @Bean
    CronTriggerFactoryBean cronTrigger(){
        CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
        bean.setJobDetail(jobDetailTwo().getObject());
        bean.setCronExpression("* * * * * ?");
        return bean;
    }

    @Bean
    SchedulerFactoryBean schedulerFactory(){
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        SimpleTrigger simpleTrigger = simpleTrigger().getObject();
        CronTrigger cronTrigger = cronTrigger().getObject();
        bean.setTriggers(simpleTrigger,cronTrigger);
        return bean;
    }
}

解释一下上述代码的含义:

  • JobDetail
    的配置方式方式:第一种方式通过MethodInvokingJobDetailFactoryBean
    类配置 JobDetail
    ,只需要指定Job的实例名称和要调用的方法即可,注册这种方式无法在创建 JobDetail
    时传递参数;第二种方式是通过 JobDetailFactoryBean
    来实现的,这种方式只需要指定 JobClass
    即可,当然可以通过 JobDataMap
    传递参数到Job中,Job中只需要提供属性名,并且提供一个相应的set方法即可接收到参数。
  • Trigger
    有多种不同实现,这里展示两种最常使用的Trigger
    SimpleTrigger
    CronTrigger
    ,这两种Trigger
    分别使用SimpleTriggerFactoryBean
    CronTriggerFactoryBean
    进行创建。在SimpleTriggerFactoryBean
    对象中,首先设置JobDetail
    ,然后通过setRepeatCount
    配置任务循环次数,setStartDelay
    配置任务启动延迟时间,setRepeatInterval
    配置任务的时间间隔。在CronTriggerFactoryBean
    对象中,则主要配置JobDetail
    和Cron表达式。
  • 最后通过SchedulerFactoryBean
    创建SchedulerFactory
    对象,然后配置Trigger
    即可。

经过这几步的配置,定时任务就配置成功了。接下来启动SpringBoot项目,可以看到控制台输出一些信息:







我就知道你“在看”


文章转载自啃饼思录,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论