写在前面

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

项目初始化

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

@Scheduled注解方式

小试牛刀

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

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

1
2
3
4
5
6
7
@EnableScheduling
@SpringBootApplication
public class TimeTaskApplication {
public static void main(String[] args) {
SpringApplication.run(TimeTaskApplication.class, args);
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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):

1
2
3
4
5
6
7
8
9
┌───────────── 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)LW可以组合使用。举个例子,如果在“日”上设置”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依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

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

1
2
3
4
5
6
7
@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类,其中的代码为:

1
2
3
4
5
6
@Component
public class FirstJob {
public void first(){
System.out.println("FirstJob--->first:"+new Date());
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
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类用于对JobDetailTrigger进行配置,相应的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@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有多种不同实现,这里展示两种最常使用的TriggerSimpleTriggerCronTrigger,这两种Trigger分别使用SimpleTriggerFactoryBeanCronTriggerFactoryBean进行创建。在SimpleTriggerFactoryBean对象中,首先设置JobDetail,然后通过setRepeatCount配置任务循环次数,setStartDelay配置任务启动延迟时间,setRepeatInterval配置任务的时间间隔。在CronTriggerFactoryBean对象中,则主要配置JobDetail和Cron表达式。
  • 最后通过SchedulerFactoryBean创建SchedulerFactory对象,然后配置Trigger即可。

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