#IT明星不是梦#【从0开始Web开发实战】SpringBoot集成Quartz实现定时任务和调度

目录:

1. SpringBoot集成Quartz

2. 实现定时任务逻辑

3. 动态创建定时任务

4. Quartz常用类

5. Quartz任务熄火策略

6. Cron表达式速成

7. Quartz使用技巧之通过JobDataMap和SchedulerContext传递数据

8. Spring框架Schedule功能的使用方法


Quartz是开源组织OpenSymphony的一个作业调度框架,采用多线程架构,可伸缩性强,可集群扩展。SpringBoot集成Quartz简单高效,只需实现Job接口,在方法execute()中添加业务逻辑


Spring框架自带了Schedule功能,能满足小型项目对定时任务的功能需求,同样支持Cron表达式、固定间隔、固定频率三种配置方式,但和Quartz相比,不支持任务信息数据库持久化。


Cron表达式

固定间隔

固定频率

传递数据

任务持久化

Quartz

Spring Schedule

×

×


本文分享SpringBoot集成和配置Quartz的方法,以及在项目中的实际应用方案,最后介绍Spring 框架自带的Schedule功能的用法。



代码文件

功能要点

SpringBoot集成Quartz

pom.xml

引入Quartz依赖spring-boot-starter-quartz

application.yml

配置Quartz属性,配置Job运行时间cron表达式

QuartzConfig.java

配置Bean: JobDetail, Trigger,读取cron运行时间配置

实现定时任务

QuartzJob.java

实现Job接口,或者继承QuartzJobBean类

动态创建任务

CheckController.java

增加REST接口/chk/job,创建一个Job定时任务,通过Context上下文传递数据。


项目代码:https://github.com/jextop/StarterApi/

示例代码:https://github.com/rickding/HelloJava/tree/master/HelloQuartz


一,SpringBoot集成Quartz

1. 新建SpringBoot项目时,选中Quartz,将自动添加Quartz依赖。

image.png

2. 已有SpringBoot项目,可以在pom.xml中直接添加Quartz依赖。

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

3. application.yml中配置Quartz,指定任务存储类型:

memory:内存方式,默认。

jdbc:数据库持久化方式,将创建数据表并保存任务信息。

spring:
  quartz:
    job-store-type: jdbc
    jdbc:
      initialize-schema: always

 

job:
  quartz:
    cron: 0 0/23 * * * ?

4. 在QuartzConfig.java中配置Bean,声明JobDetail和Trigger,使用cron表达式设置任务运行时间:

@Configuration
@ConfigurationProperties("job.quartz")
public class QuartzConfig {
    private String cron;

    @Bean
    public JobDetail quartzJob() {
        return JobBuilder.newJob(QuartzJob.class).storeDurably().build();
    }

    @Bean
    public Trigger quartzTrigger() {
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);

        return TriggerBuilder.newTrigger()
                .forJob(quartzJob())
                .withSchedule(scheduleBuilder)
                .build();
    }

    public String getCron() {
        return cron;
    }

    public void setCron(String cron) {
        this.cron = cron;
    }
}


二,定时任务QuartzJob.java,继承QuartzJobBean类实现Job接口。

JobExecutionContext中读取附加信息,执行业务逻辑。

image.png 

public class QuartzJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        LogUtil.info("quartz job ", new Date());

        try {
            JextService jextService = (JextService) context.getScheduler().getContext().get("jextService");
            if (jextService != null) {
                jextService.getInfo(true);
            }
        } catch (SchedulerException e) {
            LogUtil.info(e.getMessage());
        }
    }
}


三,动态创建定时任务

1. 增加RestController:CheckController.java

2. 增加REST接口/chk/job,创建一个定时任务,添加附加信息到Scheduler上下文中。

@GetMapping(value = "/chk/job")
public Object job() {
    JobDetail job = JobBuilder.newJob(QuartzJob.class).build();
    SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
            .forJob(job)
            .startAt(new Date())
            .build();

    Date date = null;
    try {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.getContext().put("jextService", jextService);

        date = scheduler.scheduleJob(job, trigger);
        scheduler.startDelayed(1);
    } catch (SchedulerException e) {
        e.printStackTrace();
    }

    final Date jobDate = date;
    return new HashMap<String, Object>() {{
        put("chk", "job");
        put("date", jobDate);
    }};
}

3. Postman调用REST接口创建定时任务示例

image.png 

四,Quartz常用类

Quartz提供的常用类:Scheduler, SchedulerFactory, Job, JobDetail, JobBuilder, Trigger, TriggerBuilder, ScheduleBuilder,UML类图如下:

image.png

Scheduler时Quartz调度程序的主要接口,维护一个JobDetails和Triggers的注册表,到触发时间时,调度程序将执行Job。


调度程序Scheduler实例通过SchedulerFactory工厂创建,有两个实现类DirectSchedulerFactory和StdSchedulerFactory,前者不写入持久化数据库,后者加载quartz.properties属性配置文件,将查找加载当前目录和org.quartz包。


任务是实现Job接口的一个类,实现方法execute(),可声明属性:

- @DisallowConcurrentExecution,同时只执行一个实例。

- @PersisJobDataAfterExecution,正常执行后将JobDataMap备份。

JobDetail将任务属性传递给Scheduler,通过JobBuilder创建,JobDataMap保存任务实例的状态信息。


触发器Trigger通过TriggerBuilder创建,结合ScheduleBuilder设置时间规则,可通过JobDataMap传递数据给Job。常用的两种触发器:

- SimpleTrigger:指定开始时间、结束时间、重复次数、执行间隔。

- CronTrigger:使用Cron表达式设置时间规则。


五,Quartz任务熄火策略

ScheduleBuilder设置时间规则时,可配置Misfire选项,指定执行失败熄火时的处理规则:


含义

支持ScheduleBuilder

IgnoreMisfires

马上执行,比如整点9点失败,系统10:15启动,会马上执行9点10点的任务。

SimpleSchedule

CronScheduleBuilder

CalendarIntervalScheduleBuilder

DailyTimeIntervalScheduleBuilder

FireNow

立即再次触发

SimpleSchedule

NowWithExistingCount

立即再次触发,不计入总次数。

SimpleSchedule

NowWithRemainingCount

立即再次触发,计入总次数。

SimpleSchedule

NextWithExistingCount

等待下次执行,不计入总次数。

SimpleSchedule

NextWithRemainingCount

等待下次执行,计入总次数。

SimpleSchedule

DoNothing

不做任何处理,执行下一次周期。

CronScheduleBuilder

CalendarIntervalScheduleBuilder

DailyTimeIntervalScheduleBuilder

FireAndProceed

合并下一个周期,正常执行。比如整点9点10点失败,系统10:15启动,在11点合并执行一次。

CronScheduleBuilder

CalendarIntervalScheduleBuilder

DailyTimeIntervalScheduleBuilder


六,Cron表达式速成

Cron表达式是一个字符串,定义时间规则,由6或7个时间域组成,空格分隔1张表整理清楚含义和规则,并举例常用表达式,放手边速查。

时间域序号

含义

取值范围

特殊字符

1

秒Seconds

0-59

,-*/

2

分钟Minutes

0-59

,-*/

3

小时Hours

0-23

,-*/

4

日期DayOfMonth

1-31

,-*/ ? L W C

5

月份Month

1-12

,-*/ JAN-DEC

6

星期DayOfWeek

1-7

,-*/ ? L C # SUN-SAT

7

年Year (可选)

1970-2099

,-*/

特殊字符含义

JAN-DEC 月份英语简称

SUN-SAT 星期英语简称

星期的1表示星期天,2表示星期一,依次类推

* 表示取值范围内的所有数字

/ 表示每隔固定时间触发依次,比如0/5表示从0开始每5个单位时间

- 表示两个数字之间的范围,比如3-7表示3到7之间,包含3和7

, 表示离散的枚举数字,比如2,3,5,7表示指定的这几个时间

? 只能用在日期DayOfMonth和星期DayOfWeek两个域,表示不指定,避免日期和星期的互相影响,比如指定每月的20日,不管是星期几,正确写法是:0 0 0 20 * ?,其中星期只能用?,如果使用*将触发错误。

L 只能用于日期DayOfMonth和星期DayOfWeek,用于日期时表示月份的最后一天,用于星期时不加数字表示周六,加数字表示最后一个周几,比如0 0 0 ? * 5L表示每月的最后一个星期四

W 只能用于日期DayOfMonth,表示周一到周五有效工作日,将在离指定日期的最近的有效工作日触发事件。例如在日期使用5W,如果5日是星期六,则将在最近的工作日星期五(4日)触发。如果5日是星期天,则在6日(星期一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近工作日寻找不会跨月份。

LW 两个字符连用时表示某个月最后一个工作日

# 只能用于星期DayOfWeek,表示每个月第几个星期几,比如4#2表示第二个星期三

常用表达式

0/5 * * * * ? 每5秒钟

0 0/5 * * * ? 每5分钟

0 0 6 * * ? 每天早上6点

0 0 9,13,19 * * ? 每天上午9点,下午1点,晚上7点

0 0 23-7/2,8 * * ? 每天晚上11点到早上7点之间的每两个小时,和早上8点

0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时

0 0 9-21 ? * MON-SAT 表示996每天的每小时

0 0 7 LW * ? 每月最后一个工作日早上7点

0 0 4 1 1 ? 每年的1月1日早上4点


七,Quartz使用技巧之通过JobDataMap和SchedulerContext传递数据

Job.execute()方法中实现业务逻辑时,经常需要一些附加信息。Quartz提供了JobExecutionContext上下文传递数据。

image.png 

通过JobDataMap传递数据

代码下载:https://github.com/rickding/HelloJava/tree/master/HelloQuartz

src/main/java/com/hello/quartz/

├── QuartzConfig.java

├── QuartzJob.java


代码文件

功能要点

设置数据

QuartzConfig.java

创建JobDetail或者Trigger时,调用usingJobData()设置数据

读取数据

QuartzJob.java

执行任务时,调用JobExecutionContext.getMergedJobDataMap()获取数据

1,设置数据:JobDetail和Trigger都可以调用usingJobData()方法设置数据。

@Configuration
@ConfigurationProperties("quartz")
public class QuartzConfig {
    @Bean
    public JobDetail quartzJob() {
        JobDataMap dataMap = new JobDataMap() {{
            put("job_str", "str_test");
        }};

        return JobBuilder.newJob(QuartzJob.class)
                .usingJobData(dataMap)
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger quartzTrigger() {
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        JobDataMap dataMap = new JobDataMap() {{
            put("trigger_int", 333);
        }};

        return TriggerBuilder.newTrigger()
                .forJob(quartzJob())
                .withSchedule(scheduleBuilder)
                .usingJobData(dataMap)
                .build();
    }

}

2,读取数据:从JobExecutionContext中读取JobDataMap获取数据,执行业务逻辑。

public class QuartzJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        // get data from context
        JobDataMap dataMap = context.getMergedJobDataMap();
        for (Map.Entry<String, Object> data : dataMap.entrySet()) {
            System.out.printf("%s = %s\n", data.getKey(), data.getValue());
        }

        // do work
    }
}

3,运行输出:

trigger_int = 333
job_str = str_test


通过SchedulerContext传递数据

另一个传递数据的方式是构建Scheduler时为SchedulerContext设置数据。代码详见上文中第二、第三部分:

代码下载:https://github.com/jextop/StarterApi/

src/main/java/com/starter/

├── controller/CheckController.java

├── job/QuartzJob.java

读取数据方式一样,因为JobExecutionContext.getMergedJobDataMap()已经将数据合并到一起。也可以通过调用SchedulerContext获取。


八,Spring框架Schedule功能的使用方

Spring Schedule支持异步执行定时任务,支持Cron表达式、固定间隔、固定频率三种配置方式,能满足小型项目对定时任务的功能需求。示例代码:https://github.com/rickding/HelloJava/tree/master/HelloQuartz

src/main/java/com/hello/quartz/

├── QuartzApplication.java

├── ScheduledJob.java


代码文件

功能要点

开启功能

QuartzApplication.java

SpringApplication增加注解:

@EnableScheduling // 打开Schedule功能

@EnableAsysnc // 异步方式执行定时任务

配置任务

ScheduleJob.java

声明定时任务,功能注解:

@Async // 异步方式执行,声明在类上面时,作用于类里面包含的任务

@Scheduled // 声明定时任务执行时间

@EnableAsync

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

1,cron表达式声明定时任务,比如每10秒执行一次:

@Async
@Scheduled(cron = "0/10 * * * * ?")
public void scheduledCron() {
    System.out.printf("scheduled cron: %s\n", new Date());
}

2,固定频率执行定时任务,比如每20秒执行一次:

@Scheduled(fixedRate = 1000 * 20, initialDelay = 1000 * 20)
public void scheduledRate() {
    System.out.printf("fixedRate: %s\n", new Date());
}

3,固定间隔执行定时任务,比如每20秒执行一次:

@Scheduled(fixedDelay = 1000 * 30, initialDelay = 1000 * 30)
public void scheduledDelay() {
    System.out.printf("fixedDelay: %s\n", new Date());
}

注意固定频率和固定间隔两类定时任务,默认在程序启动时就会执行一次,可以通过指定initialDelay参数控制首次执行时间。

    scheduled cron: Sun Feb 09 18:09:19 CST 2020
    fixedRate: Sun Feb 09 18:09:28 CST 2020
    scheduled cron: Sun Feb 09 18:09:38 CST 2020

--------------------------------

如果您觉得这篇文章对您有帮助,请点个“赞”,博主感激不尽!

Jext技术社区专注领域:软件工程实践,JIRA研发管理分布式系统架构,软件质量保障

image.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章