在项⽬开发中,经常需要定时任务来帮助我们来做⼀些内容,⽐如定时派息、跑批对账、业务监控等。
Spring Boot
体系中现在有两种⽅案可以选择,第⼀种是
Spring Boot
内置的⽅式简单注解就可以使⽤,当然
如果需要更复杂的应⽤场景还是得
Quartz
上场,
Quartz
⽬前是
Java
体系中最完善的定时⽅案。
⾸先来看看
Spring Boot
⾃带的定时⽅案。
Spring Boot 内置定时
pom 包配置
pom 包⾥⾯只需要引⼊ Spring Boot Starter 包即可,Spring Boot Starter 包中已经内置了定时的⽅法。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
启动类开启定时
在启动类上⾯加上
@EnableScheduling
即可开启定时:
@Spring BootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
创建定时任务实现类
使⽤
Spring Boot
⾃带的定时⾮常的简单,只需要在⽅法上⾯添加
@Scheduled
注解即可。
定时任务
1
:
@Component
public class SchedulerTask {
private int count=0;
@Scheduled(cron="*/6 * * * * ?")
private void process(){
System.out.println("this is scheduler task runing "+(count++));
}
}
设置
process()
每隔六秒执⾏⼀次,并统计执⾏的次数。
我们还有另外的⼀种⽅案来设置,固定时间周期执⾏⽅法,来看定时任务
2
:
@Component
public class Scheduler2Task {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm
:ss");
@Scheduled(fixedRate = 6000)
public void reportCurrentTime() {
System.out.println("现在时间:" + dateFormat.format(new Date()));
}
}
启动项⽬之后,就会在控制台看到打印的结果。
结果如下:
this is scheduler task runing 0
现在时间:09:44:17
this is scheduler task runing 1
现在时间:09:44:23
this is scheduler task runing 2
现在时间:09:44:29
this is scheduler task runing 3
现在时间:09:44:35
说明两个⽅法都按照固定
6
秒的频率来执⾏。
参数说明
@Scheduled
参数可以接受两种定时的设置,⼀种是我们常⽤的
cron="*/6 * * * * ?"
,⼀种是
fifixedRate =
6000
,两种都可表示固定周期执⾏定时任务。
fifixedRate 说明
- @Scheduled(fifixedRate = 6000):上⼀次开始执⾏时间点之后 6 秒再执⾏。
- @Scheduled(fifixedDelay = 6000):上⼀次执⾏完毕时间点之后 6 秒再执⾏。
- @Scheduled(initialDelay=1000, fifixedRate=6000):第⼀次延迟 1 秒后执⾏,之后按 fifixedRate 的规则 每 6 秒执⾏⼀次。
cron 说明
cron
⼀共有七位,最后⼀位是年,
Spring Boot
定时⽅案中只需要设置六位即可:
- 第⼀位,表示秒,取值 0 ~ 59;
- 第⼆位,表示分,取值 0 ~ 59;
- 第三位,表示⼩时,取值 0 ~ 23;
- 第四位,⽇期天/⽇,取值 1 ~ 31;
- 第五位,⽇期⽉份,取值 1~12;
- 第六位,星期,取值 1 ~ 7,星期⼀,星期⼆...,注,不是第 1 周、第 2 周的意思,另外,1 表示星期 天,2 表示星期⼀;
- 第七位,年份,可以留空,取值 1970 ~ 2099。
cron
中,还有⼀些特殊的符号,含义如下:
- (*)星号,可以理解为每的意思,每秒、每分、每天、每⽉、每年...。
- (?)问号,问号只能出现在⽇期和星期这两个位置,表示这个位置的值不确定,每天 3 点执⾏,因此 第六位星期的位置,是不需要关注的,就是不确定的值;同时,⽇期和星期是两个相互排斥的元素,通 过问号来表明不指定值,⽐如 1 ⽉ 10 ⽇是星期⼀,如果在星期的位置另指定星期⼆,就前后冲突⽭盾 了。
- (-)减号,表达⼀个范围,如在⼩时字段中使⽤“10 ~ 12”,则表示从 10 到 12 点,即 10、11、12。
- (,)逗号,表达⼀个列表值,如在星期字段中使⽤“1、2、4”,则表示星期⼀、星期⼆、星期四。
- (/)斜杠,如 x/y,x 是开始值,y 是步⻓,⽐如在第⼀位(秒),0/15 就是从 0 秒开始,每隔 15 秒执 ⾏⼀次,最后就是 0、15、30、45、60,另 */y,等同于 0/y。
下⾯列举⼏个常⽤的例⼦。
- 0 0 3 * * ? :每天 3 点执⾏;
- 0 5 3 * * ?:每天 3 点 5 分执⾏;
- 0 5 3 ? * *:每天 3 点 5 分执⾏,与上⾯作⽤相同;
- 0 5/10 3 * * ?:每天 3 点的 5 分、15 分、25 分、35 分、45 分、55 分这⼏个时间点执⾏;
- 0 10 3 ? * 1:每周星期天,3 点 10 分执⾏,注,1 表示星期天;
- 0 10 3 ? * 1#3:每个⽉的第三个星期,星期天执⾏,# 号只能出现在星期的位置。
以上就是
Spring Boot
⾃定的定时⽅案,使⽤起来⾮常的简单⽅便。
Quartz
Quartz 介绍
Quartz
是
OpenSymphony
开源组织在
Job Scheduling
领域⼜⼀个开源项⽬,是完全由
Java
开发的⼀个开
源任务⽇程管理系统,
“
任务进度管理器
”
就是⼀个在预先确定(被纳⼊⽇程)的时间到达时,负责执⾏(或
者通知)其他软件组件的系统。
Quartz
是⼀个开源的作业调度框架,它完全由
Java
写成,并设计⽤于
J2SE
和
J2EE
应⽤中,它提供了巨⼤的灵活性⽽不牺牲简单性。
当定时任务愈加复杂时,使⽤
Spring
注解
@Schedule
已经不能满⾜业务需要。
Quartz 的优点:
- 丰富的 Job 操作 API;
- ⽀持多种配置;
- Spring Boot ⽆缝集成;
- ⽀持持久化;
- ⽀持集群;
- Quartz 还⽀持开源,是⼀个功能丰富的开源作业调度库,可以集成到⼏乎任何 Java 应⽤程序中。
Quartz 体系结构
明⽩
Quartz
怎么⽤,⾸先要了解
Job
(任务)、
JobDetail
(任务信息)、
Trigger
(触发器)和
Scheduler
(调度器)这
4
个核⼼的概念。
(
1
)
Job
:是⼀个接⼝,只定义⼀个⽅法
execute
(
JobExecutionContext context
),在实现接⼝的
execute
⽅法中编写所需要定时执⾏的
Job
(任务),
JobExecutionContext
类提供了调度应⽤的⼀些信息;
Job
运⾏时的信息保存在
JobDataMap
实例中。
(
2
)
JobDetail
:
Quartz
每次调度
Job
时,都重新创建⼀个
Job
实例,因此它不接受⼀个
Job
的实例,相反
它接收⼀个
Job
实现类(
JobDetail
,描述
Job
的实现类及其他相关的静态信息,如
Job
名字、描述、关联
监听器等信息),以便运⾏时通过
newInstance()
的反射机制实例化
Job
。
(
3
)
Trigger
:是⼀个类,描述触发
Job
执⾏的时间触发规则,主要有
SimpleTrigger
和
CronTrigger
这两个
⼦类。当且仅当需调度⼀次或者以固定时间间隔周期执⾏调度,
SimpleTrigger
是最适合的选择;⽽
CronTrigger
则可以通过
Cron
表达式定义出各种复杂时间规则的调度⽅案:如⼯作⽇周⼀到周五的
15
:
00
~ 16
:
00
执⾏调度等。
(
4
)
Scheduler
:调度器就相当于⼀个容器,装载着任务和触发器,该类是⼀个接⼝,代表⼀个
Quartz
的独
⽴运⾏容器,
Trigger
和
JobDetail
可以注册到
Scheduler
中,两者在
Scheduler
中拥有各⾃的组及名称,组
及名称是
Scheduler
查找定位容器中某⼀对象的依据,
Trigger
的组及名称必须唯⼀,
JobDetail
的组和名称
也必须唯⼀(但可以和
Trigger
的组和名称相同,因为它们是不同类型的)。
Scheduler
定义了多个接⼝⽅
法,允许外部通过组及名称访问和控制容器中
Trigger
和
JobDetail
。
四者其关系如下图所示:
Job
为作业的接⼝,为任务调度的对象;
JobDetail
⽤来描述
Job
的实现类及其他相关的静态信息;
Trigger
做为作业的定时管理⼯具,⼀个
Trigger
只能对应⼀个作业实例,⽽⼀个作业实例可对应多个触发器;
Scheduler
做为定时任务容器,是
Quartz
最上层的东⻄,它提携了所有触发器和作业,使它们协调⼯作,每
个
Scheduler
都存有
JobDetail
和
Trigger
的注册,⼀个
Scheduler
中可以注册多个
JobDetail
和多个
Trigger
。
Spring Boot 和 Quartz
Spring Boot 2.0
提供了
spring-boot-starter-quartz
组件集成
Quartz
,让我们在项⽬中使⽤
Quartz
变得简 单。
配置内容
配置 pom.xml
添加
spring-boot-starter-quartz
组件:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
简单示例
配置完成之后先来做⼀个最简单的示例,使⽤
Quartz
定时输出
Hello World
。
⾸先定义⼀个
Job
需要继承
QuartzJobBean
,示例中
Job
定义⼀个变量
Name
,⽤于在定时执⾏的时候传
⼊。
public class SampleJob extends QuartzJobBean {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
protected void executeInternal(JobExecutionContext context)
throws JobExecutionException {
System.out.println(String.format("Hello %s!", this.name));
}
}
接下来构建
JobDetail
,并且构建时传⼊
name
属性的值,构建
JobTrigger
和
scheduleBuilder
,最后使⽤
Scheduler
启动定时任务。
@Configuration
public class SampleScheduler {
@Bean
public JobDetail sampleJobDetail() {
return JobBuilder.newJob(SampleJob.class).withIdentity("sampleJob")
.usingJobData("name", "World").storeDurably().build();
}
@Bean
public Trigger sampleJobTrigger() {
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedu
le()
.withIntervalInSeconds(2).repeatForever();
return TriggerBuilder.newTrigger().forJob(sampleJobDetail())
.withIdentity("sampleTrigger").withSchedule(scheduleBuilder).build
();
}
}
- JobBuilder ⽆构造函数,只能通过 JobBuilder 的静态⽅法 newJob(Class<? extends Job> jobClass)⽣ 成 JobBuilder 实例。
- withIdentity ⽅法可以传⼊两个参数 withIdentity(String name,String group) 来定义 TriggerKey,也可以 不设置,像上⽂示例中会⾃动⽣成⼀个独⼀⽆⼆的 TriggerKey ⽤来区分不同的 Trigger。
启动项⽬后每隔两秒输出:
Hello World!
Hello World!
Hello World!
Hello World!
...
CronSchedule 示例
CronSchedule
可以设置更灵活的使⽤⽅式,定时设置可以参考上⾯的
cron
表达式。
⾸先定义两个
Job
:
public class ScheduledJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException
{
System.out.println("schedule job1 is running ...");
}
}
ScheduledJob2
和
ScheduledJob
代码基本⼀致。
按照使⽤
Quartz
的逻辑,构建
jobDetail
、
CronTrigger
,最后使⽤
scheduler
关联
jobDetail
和
CronTrigger
。
scheduleJob1
设置每间隔
6
秒执⾏⼀次。
private void scheduleJob1(Scheduler scheduler) throws SchedulerException{
JobDetail jobDetail = JobBuilder.newJob(ScheduledJob.class) .withIdentity("job
1", "group1").build();
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/6 *
* * * ?");
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger1",
"group1") .withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail,cronTrigger);
}
- CronScheduleBuilder.cronSchedule("0/6 * * * * ?"),按照 cron 表达式设置定时任务的执⾏周期。
ScheduleJob2
的内容和
ScheduleJob1
基本⼀致,时间设置为间隔
12
秒执⾏⼀次。
使⽤
Scheduler
启动两个定时任务。
public void scheduleJobs() throws SchedulerException {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduleJob1(scheduler);
scheduleJob2(scheduler);
}
何时触发定时任务
我们有两种⽅案来触发
CronSchedule
定时任务,⼀种是启动时调⽤
scheduleJobs()
来启动定时任务,另外
⼀种⽅案使⽤
Spring Boot
⾃带的
Scheduled
在特定时间触发启动。
第⼀种⽅案,启动时触发定时任务:
@Component
public class MyStartupRunner implements CommandLineRunner {
@Autowired
public CronSchedulerJob scheduleJobs;
@Override
public void run(String... args) throws Exception {
scheduleJobs.scheduleJobs();
System.out.println(">>>>>>>>>>>>>>>定时任务开始执⾏<<<<<<<<<<<<<");
}
}
定时⼀个
Runner
,继承
CommandLineRunner
并重新
run
⽅法,在
run
⽅法中调⽤
scheduleJobs()
来启动
定时任务。
第⼆种⽅案,特定时间启动定时任务:
@Configuration
@EnableScheduling
@Component
public class SchedulerListener {
@Autowired
public CronSchedulerJob scheduleJobs;
@Scheduled(cron="0 30 11 25 11 ?")
public void schedule() throws SchedulerException {
scheduleJobs.scheduleJobs();
}
}
启动项⽬后每隔
6
秒输出
job1
内容,每隔
12
秒输出
job2
内容,再加上上⾯示例每两秒输出的
Hello
World
,输出内容如下:
GitChat
Hello World!
Hello World!
Hello World!
schedule job1 is running ...
Hello World!
Hello World!
Hello World!
schedule job1 is running ...
schedule job2 is running ...
...
⼀般情况下,建议使⽤第⼀种⽅案来启动定时任务;第⼆种⽅案设置固定⽇期时,需要考虑重复启动定时任
务的情况,重复启动定时任务会报错。
注意,两种启动⽅案,在项⽬中选择⼀种使⽤即可,否则会导致重复启动定时任务⽽报错。
总结
通过上⾯的示例可以看出,如果仅需要执⾏简单定时任务,就可以使⽤
Spring Boot
⾃带
Scheduled
,⾮常
简单、⽅便;但如果需要在项⽬中执⾏⼤量的批任务处理时,可以采⽤
Quartz
来解决,
Spring Boot 2.0
中
提供了对
Quartz
的⽀持,让我们在项⽬使⽤的过程中更加的灵活简洁。