文章目录

  • 一、介绍
  • 二、Schedule组件
  • 1、介绍
  • 2、Schedule定时任务实现
  • 2.1 配置线程池
  • 2.2 新建具体任务
  • 2.3 开启Schedule
  • 3、定时任务的动态修改
  • 三、quartz组件
  • 1、介绍
  • 2、重要概念
  • 3、quartz内存配置实战
  • 3.1 引入依赖
  • 3.2 创建job
  • 3.3 调度器Scheduler绑定
  • 3.4 yml配置
  • 4、Quartz持久化配置
  • 四、cron表达式
  • 1、简介
  • 2、字段含义
  • 3、常用表达式实例


一、介绍

SpringBoot可以有两种方式实现定时任务,schedule和schedule,这两种组件都可以和Spring进行整合,区别如下表所示

组件名称

cron

持久化

开发难以程度

schedule

支持

不支持

非常简单

quartz

支持

支持

复杂

二、Schedule组件

1、介绍

SpringBoot内置了Sping Schedule定时框架,通过注解驱动方式添加所注解方法到定时任务,根据配置定时信息定时执行

2、Schedule定时任务实现

2.1 配置线程池

@Configuration
@Slf4j
publicclass ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());    
    }
    @Bean
    public Executor taskExecutor(){
        return Executors.newScheduledThreadPool(10);
    }
}

2.2 新建具体任务

如果想加个开关控制是否启用定时任务,可以使用@ConditionalOnProperty注解,并同时在配置文件中设置scheduling.enabled=false

@Component
@Slf4j
//@ConditionalOnProperty(prefix = "scheduling", name = "enabled", havingValue = "true")
public class SbScheduleTask1 {
    @Async
    @Scheduled(cron = "*/2 * * * * ?")
    public void task1() throws InterruptedException {
        log.error("我是task1111,我需要执行 10s 钟的时间,我的线程的 id == > {},时间 == >{}", Thread.currentThread().getId(), new Date());
        Thread.sleep(10000);
        log.error("task1111 ending ,我的线程的 id == > {} , 时间 == > {}", Thread.currentThread().getId(), new Date());
    }
    @Async
    @Scheduled(cron = "*/4 * * * * ?")
    public void task2() throws InterruptedException {
        log.error("我是task2222,我需要执行 2s 钟的时间,我的线程的 id == > {},时间 == >{}", Thread.currentThread().getId(), new Date());
        Thread.sleep(2000);
        log.error("task2222 ending ,我的线程的 id == > {} , 时间 == > {}", Thread.currentThread().getId(), new Date());
    }
}

2.3 开启Schedule

@SpringBootApplication
@EnableAsync
// 开启定时任务
@EnableScheduling
@MapperScan(basePackages = {"com.shawn.springboot.*.mapper"})
public class ScheduleApplication {
 
  public static void main(String[] args) {
    SpringApplication.run(ScheduleApplication.class, args);
  }
}

3、定时任务的动态修改

创建task-config.ini配置文件

printTime.cron=0/10 * * * * ?

创建配置文件

@Data
@Slf4j
@Component
@PropertySource("classpath:/task-config.ini")
public class ScheduleTask implements SchedulingConfigurer {

    @Value("${printTime.cron}")
    private String cron;

    private Long timer = 10000L;


    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 设置线程池,一般是设置一个全局的
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(10);
        taskScheduler.initialize();
        taskRegistrar.setTaskScheduler(taskScheduler);


        // 动态使用cron表达式设置循环间隔
        taskRegistrar.addTriggerTask(new Runnable() {
            @Override
            public void run() {
                log.info("Current time: {}", LocalDateTime.now());
            }
        }, new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                // 使用CronTrigger触发器,可动态修改cron表达式来操作循环规则
//                CronTrigger cronTrigger = new CronTrigger(cron);
//                Date nextExecutionTime = cronTrigger.nextExecutionTime(triggerContext);

                // 使用不同的触发器,为设置循环时间的关键,区别于CronTrigger触发器,该触发器可随意设置循环间隔时间,单位为毫秒
                PeriodicTrigger periodicTrigger = new PeriodicTrigger(timer);
                Date nextExecutionTime = periodicTrigger.nextExecutionTime(triggerContext);
                return nextExecutionTime;
            }
        });
    }
}

修改接口

@Slf4j
@RestController
public class TestController {

    private final ScheduleTask scheduleTask;

    @Autowired
    public TestController(ScheduleTask scheduleTask) {
        this.scheduleTask = scheduleTask;
    }

    @GetMapping("/updateCron")
    public String updateCron(String cron) {
        log.info("new cron :{}", cron);
        scheduleTask.setCron(cron);
        return "ok";
    }

    @GetMapping("/updateTimer")
    public String updateTimer(Long timer) {
        log.info("new timer :{}", timer);
        scheduleTask.setTimer(timer);
        return "ok";
    }
}

三、quartz组件

1、介绍


Quartz是一个功能强大的开源任务调度库,几乎可以集成到任何Java应用程序中,无论是超小型的独立应用还是超大型电子商务系统。

它常用于企业级应用中:

  • Driving Process Workflow:当新订单下达,可以安排一个30分钟内触发的任务,检查订单状态。
  • System Maintenance:安排每个工作日晚上11点将数据库内容转储到文件的任务。
  • Providing reminder services:提供提醒服务。

Quartz还支持集群模式和对JTA事务。

2、重要概念

Scheduler :和调度程序交互的主要API

  • 生命周期从SchedulerFactoru创建它开始,到调用shutdown方法结束
  • 一旦Scheduler创建,任何关于scheduling相关的事,他都为所欲为:添加、删除、列出所有的Jobs和triggers、暂停触发器等
  • 在start方法之前,不会做任何事情

**Job:**被调度器调度的任务组件接口,即定时任务执行的方法

  • 当Job的触发器触发时,调度程序的工作线程将调用execute方法
  • 该方法接收一个JobExecutionContext 对象,为Job实例提供了丰富的运行时环境信息,比如:scheduler、trigger、jobDataMap、job、calendar、各种time等

JobDetail :用于定义任务

  • JobDetail对象由Quartz客户端在将job加入Scheduler提供
  • 它包含了不同为job设置的属性,还有可以用来为job储存状态信息的JobDataMap
  • 注意它和Job的区别,它实际上是Job实例的属性。【Job定义如何执行,JobDetail定义有何属性】

Trigger :触发任务执行

  • 触发器可能具有与之关联的JobDataMap,以便于将特定于触发器触发的参数传递给Job
  • Quartz提供了几种不同的触发器,SimpleTrigger和CronTrigger比较常用
  • 如果你需要一次性执行作业或需要在给定的时间触发一个作业并重复执行N次且有两次执行间有延时delay,SimpleTrigger较为方便
  • 如果你希望基于类似日期表触发执行任务,CronTrgger推荐使用

JobBuilder :用于构建JobDetail的

TriggerBuilder :用于构建Trigger的

3、quartz内存配置实战

3.1 引入依赖

spring-boot-starter-quartz这个依赖是SpringBoot与Quartz的整合

<!-- 实现对 Quartz 的自动化配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

3.2 创建job

这里创建了两个job,我们在创建Job的时候,可以实现Job接口,也可以继承QuartzJobBean。

QuartzJobBean实现了Job,并且定义了公用的execute方法,子类可以继承QuartzJobBean并实现executeInternal方法。

@Slf4j
public class FirstJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String now = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
        log.info("当前的时间: " + now);
    }
}

//--------------------
@Slf4j
public class SecondJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String now = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
        log.info("SecondJob执行, 当前的时间: " + now);
    }
}

3.3 调度器Scheduler绑定

自动绑定,这里使用了SimpleScheduleBuilder

@Configuration
public class QuartzConfig {

    private static final String ID = "SUMMERDAY";

    @Bean
    public JobDetail jobDetail1() {
        return JobBuilder.newJob(FirstJob.class)
                .withIdentity(ID + " 01")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger trigger1() {
        // 简单的调度计划的构造器
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(5) // 频率
                .repeatForever(); // 次数

        return TriggerBuilder.newTrigger()
                .forJob(jobDetail1())
                .withIdentity(ID + " 01Trigger")
                .withSchedule(scheduleBuilder)
                .build();
    }
}

手动绑定,这里使用了CronScheduleBuilder

@Component
public class JobInit implements ApplicationRunner {

    private static final String ID = "SUMMERDAY";

    @Autowired
    private Scheduler scheduler;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        JobDetail jobDetail = JobBuilder.newJob(FirstJob.class)
                .withIdentity(ID + " 01")
                .storeDurably()
                .build();
        CronScheduleBuilder scheduleBuilder =
                CronScheduleBuilder.cronSchedule("0/5 * * * * ? *");
        // 创建任务触发器
        Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withIdentity(ID + " 01Trigger")
                .withSchedule(scheduleBuilder)
                .startNow() //立即執行一次任務
                .build();
        // 手动将触发器与任务绑定到调度器内
        scheduler.scheduleJob(jobDetail, trigger);
    }
}

3.4 yml配置

spring:
  # Quartz 的配置,对应 QuartzProperties 配置类
  quartz:
    job-store-type: memory # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。
    auto-startup: true # Quartz 是否自动启动
    startup-delay: 0 # 延迟 N 秒启动
    wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
    overwrite-existing-jobs: false # 是否覆盖已有 Job 的配置
    properties: # 添加 Quartz Scheduler 附加属性
      org:
        quartz:
          threadPool:
            threadCount: 25 # 线程池大小。默认为 10 。
            threadPriority: 5 # 线程优先级
            class: org.quartz.simpl.SimpleThreadPool # 线程池类型

4、Quartz持久化配置

Quartz持久化配置提供了两种存储器:

类型

优点

缺点

RAMJobStore

不要外部数据库,配置容易,运行速度快

因为调度程序信息是存储在被分配给 JVM 的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。另外因为存储到JVM内存里面,所以可以存储多少个 Job 和 Trigger 将会受到限制

JDBC 作业存储

支持集群,因为所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务

运行速度的快慢取决与连接数据库的快慢

四、cron表达式

1、简介

表达式网站:http://cron.qqe2.com/

表达式基本公式 * * * * * *七个*分别对应单位为:秒,分,时,日,月,星期,年

2、字段含义

字段

取值范围

特殊字符


0~59的整数

,- * /


0~59的整数

,- * /


0~23的整数

,- * /


1~31的整数

,- * ? / L W


1~12的整数

,- * /

星期

17的整数或者SUNSAT(1=SUN)

,- * ? / L #


1970~2099

,- * /

  • “,” :表示列出枚举值,例如分钟使用5,20,则表示5和20分钟各执行一次
  • “-” :表示范围,例如分钟使用5-20,表示5-20分钟每分钟触发一次
  • "" :表示匹配该域任意值,例如分钟使用,表示每分钟都会执行一次
  • “/” :表示起始时间开始触发,以后每隔多长时间触发一次,例如秒使用0/3,表示从0开始触发,后每三分钟触发一次
  • “?”:只能在日和星期使用,表示匹配任意值,但实际不会;因为日和星期可能会存在冲突,如果想表示每月20号0点执行,则需要写为 0 0 0 20 * ?,星期位必须写为?,虽然概念上*也表示通配
  • “L” :表示最后,只出现在日和星期;例如在星期的5L,表示最后一个星期五触发
  • “W” :表示有效工作日(周一-周五),只出现在日,如果指定的当天在某月刚好为周末,则就近取周五或周一执行
  • “LW” :连用表示每个月最后一个星期五,只在日使用
  • “#” :用于确定第几个星期的星期几,只在星期使用;例如2#3,表示在每月的第三个星期一

3、常用表达式实例

  • 0/3 * * * * ? :表示每三秒钟执行一次
  • 0 0 2 1 * ? :表示每月1号凌晨两点执行任务
  • 0 15 10 ? * MON-FRI :表示周一到周五每天早上10:15执行
  • 0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发