Spring Boot 整合 Quartz 实现定时任务

Posted by 暮夏有五 on 2021-01-22
Estimated Reading Time 6 Minutes
Words 1.7k In Total
Viewed Times

在 Spring + SpringMVC 环境中,一般来说,要实现定时任务,我们有两中方案,一种是使用 Spring 自带的定时任务处理器 @Scheduled 注解,另一种就是使用第三方框架 Quartz,一般在项目中,除非定时任务涉及到的业务实在是太简单就使用 @Scheduled 注解来解决定时任务,否则大部分情况可能都是使用 Quartz 来做定时任务。Spring Boot 源自 Spring+SpringMVC ,因此天然具备这两个 Spring 中的定时任务实现策略,当然也支持 Quartz

创建项目


引入依赖

pom.xml 中引入 quartz 依赖

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

相关配置

在入口类中添加开启定时任务的注解:

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

实现方式


Quartz 在使用过程中,有两个关键概念,一个是 JobDetail(要做的事情),另一个是触发器(什么时候做),要定义 JobDetail,需要先定义 JobJob 的定义有两种方式。

方式一

直接定义一个Bean:

1
2
3
4
5
6
7
@Component
public class MyJob1 {

public void sayHello() {
System.out.println("MyJob1>>>" + new Date());
}
}

关于这种定义方式说两点:

  • 首先将这个 Job 注册到 Spring 容器中。
  • 这种定义方式有一个缺陷,就是无法传参。

方式二

则是继承 QuartzJobBean 并实现默认的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyJob2 extends QuartzJobBean {

HelloService helloService;

public HelloService getHelloService() {
return helloService;
}

public void setHelloService(HelloService helloService) {
this.helloService = helloService;
}

@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
helloService.sayHello();
}
}
1
2
3
4
5
6
public class HelloService {

public void sayHello() {
System.out.println("Hello service >>>" + new Date());
}
}

和第1种方式相比,这种方式支持传参,任务启动时,executeInternal 方法将会被执行。

Job 有了之后,接下来创建类,配置 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
49
50
51
@Configuration
public class QuartzConfig {

@Bean
MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean() {
MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
bean.setTargetBeanName("myJob1");
bean.setTargetMethod("sayHello");
return bean;
}

@Bean
JobDetailFactoryBean jobDetailFactoryBean() {
JobDetailFactoryBean bean = new JobDetailFactoryBean();
bean.setJobClass(MyJob2.class);
JobDataMap map = new JobDataMap();
map.put("helloService", helloService());
bean.setJobDataMap(map);
return bean;
}

@Bean
SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
bean.setStartTime(new Date());
bean.setRepeatCount(5);
bean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());
bean.setRepeatInterval(3000);
return bean;
}

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

@Bean
SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
bean.setTriggers(cronTrigger().getObject(), simpleTriggerFactoryBean().getObject());
return bean;
}

@Bean
HelloService helloService() {
return new HelloService();
}
}

关于这个配置说如下几点:

  1. JobDetail 的配置有两种方式:MethodInvokingJobDetailFactoryBeanJobDetailFactoryBean
  2. 使用 MethodInvokingJobDetailFactoryBean 可以配置目标 Bean 的名字和目标方法的名字,这种方式不支持传参。
  3. 使用 JobDetailFactoryBean 可以配置 JobDetail,任务类继承自 QuartzJobBean,这种方式支持传参,将参数封装在 JobDataMap 中进行传递。
  4. Trigger 是指触发器,Quartz 中定义了多个触发器,这里向大家展示其中两种的用法,SimpleTriggerCronTrigger
  5. SimpleTrigger 有点类似于前面说的 @Scheduled 的基本用法。
  6. CronTrigger 则有点类似于 @Scheduledcron 表达式的用法。

全部定义完成后,启动 Spring Boot 项目就可以看到定时任务的执行了。

附:cron 表达式


cron 是 Linux 系统用来设置计划任务的,比如:每天晚上 12 点重启服务器。

一个 cron 表达式具体表现就是一个字符串,这个字符串中包含 6~7 个字段,字段之间是由空格分割的,每个字段可以由任何允许的值以及允许的特殊字符所构成,下面表格列出了每个字段所允许的值和特殊字符

字段 允许值 允许的特殊字符
0-59 , - * /
0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * / L C #
年(可选) 留空, 1970-2099 , - * /
  • * 字符被用来指定所有的值。如:* 在分钟的字段域里表示“每分钟”。
  • - 字符被用来指定一个范围。如:10-12 在小时域意味着“10点、11点、12点”
  • , 字符被用来指定另外的值。如:MON,WED,FRI 在星期域里表示“星期一、星期三、星期五”.
  • ? 字符只在日期域和星期域中使用。它被用来指定“非明确的值”。当你需要通过在这两个域中的一个来指定一些东西的时候,它是有用的。
  • L 字符指定在月或者星期中的某天(最后一天)。即 “Last” 的缩写。但是在星期和月中 “L” 表示不同的意思,如:在月字段中 “L” 指月份的最后一天 “1月31日,2月28日”,如果在星期字段中则简单的表示为“7”或者“SAT”。如果在星期字段中在某个 value 值得后面,则表示 “某月的最后一个星期 value” ,如 “6L” 表示某月的最后一个星期五。
  • W 字符只能用在月份字段中,该字段指定了离指定日期最近的那个星期日。
  • # 字符只能用在星期字段,该字段指定了第几个星期 value 在某月中

每一个元素都可以显式地规定一个值(如 6),一个区间(如 9-12 ),一个列表(如 9,11,13 )或一个通配符(如 *)。“月份中的日期”和“星期中的日期”这两个元素是互斥的,因此应该通过设置一个问号(?)来表明你不想设置的那个字段。

表达式 意义
0 0 12 * * ? 每天中午 12 点触发
0 15 10 ? * * 每天上午 10:15 触发
0 15 10 * * ? 每天上午 10:15 触发
0 15 10 * * ? * 每天上午 10:15 触发
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 触发

更多干货请移步:https://antoniopeng.com


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !