Spring Boot调度任务源码与教程 - Thanh


调度是指在特定时间或特定时间间隔后执行任务,以带来减少时间、减少资源、最大化吞吐量的好处。调度的诞生是为了处理诸如收集每日报告、每月报告或在一段时间后处理数据之类的任务。
Spring 提供了一组大部分位于spring-context模块中的注解、类和接口。它们都放在名为org.springframework.scheduling.*包下.
这个org.springframework.scheduling包下有四个子包 :

  • annotation:一组注释有助于以声明方式开发调度。
  • concurrent : java.util.concurrent 和javax.enterprise.concurrent包的调度便利类,允许将 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 设置为 Spring 上下文中的 bean。提供对本机 java.util.concurrent接口以及 Spring 的支持。
  • config :类和接口处理与配置相关的事情。
  • support : 为调度提供通用支持。例如石英cron 支持。

最重要的注释:
  • @EnableScheduling
  • @Scheduled
  • @Component
  • @Configuation
  • @org.springframework.scheduling.annotation.EnableAsync

@EnableScheduling注解告诉 Spring 它应该检测用@Scheduled. 这个主要用于@Configuration.
@Scheduled用于标记要调度的方法的注释。它包含不同的属性来指定何时应该调用该方法。这个仅在方法级别可用,并且只有无参数方法可以用@Scheduled. 我们将在下一节中学习使用这个注解。
对于@Component 注解,实际上不是必需的,但它表示使调度工作非常重要的一点。该调度方法仅在 spring 管理的 bean 中定义时才可用。这意味着我们必须定义里面有注解的类的调度方法@Sevice,@Repository,@Controller,或@Component。在我看来,@Service 和@Component 最适合这种情况。
与@Component 一样,Configuration 注解不属于调度包,但对于更高复杂性的使用,应用 spring 调度很有用。
 
调度任务中有一些属性可以配置调度方法。我们应该只关注五个重要的属性:
  • cron: 用于指定 cron 表达式以调度方法执行。例如0 0/1 * * * ?→ 每分钟。
  • fixedDelay: 用于指定上次调用结束和下一次调用开始之间的固定时间段(以毫秒为单位)。此属性需要一个长值,要指定字符串,请改用 fixedDelayString。
  • fixedRate:用于指定每 n 毫秒运行一次计划任务的时间段。它不检查任务的任何先前执行。像fixedDelay一样,这个属性需要一个长值,来指定一个字符串,使用fixedRateString代替。
  • initialDelay : 第一次执行之前延迟的毫秒数。
  • zone:将解析 cron 表达式的时区。这对于多区域或时区系统特别有用。

  
源代码案例
在我的风格中,我通常创建一个类来配置项目中的特定配置。所以我创建了 SchedulingConf 类,用于配置与调度相关的所有内容。
package com.programmingsharing.springboottopics.conf;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
@EnableAsync
public class SchedulingConf {

}

这里我们需要使用我之前提到的@Configuration 将这个类标记为Configuration 类。通过EnableScheduling注解启用调度,这是Spring调度工作所必需的。

  • 第一个例子:每分钟说一次问候:

package com.programmingsharing.springboottopics.tasks;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.Instant;

@Component
public class CronTask {

    @Scheduled(cron = "0 0/1 * * * ?") // Every one minute
    public void greet() throws InterruptedException {
        System.out.println(
"Hi. Greeting from scheduled task." + Instant.now().toString());
        Thread.sleep(2*60*1000);
    }
}

看看上面的代码片段,两分钟内会打印多少条问候信息?
在这个例子中,我们使用 cron 表达式来定义greet()调用方法的时间,并期望它每分钟调用一次。不幸的是,该方法的执行时间超过 1 分钟,因此只有在上次执行完成后的下一个时钟分钟开始时才会启动下一次执行。
  • 第二个例子:调度任务遵循特定的时间间隔

对于这个例子,我们做一个常见的场景,我们的服务器需要处理客户端提交的文件,特性规范不需要在收到文件后立即处理文件,所以我们可以安排一个任务每十分钟处理一次文件(批处理)。
@Scheduled(fixedDelay = 10*60*1000, initialDelay = 10_000)
public void processFiles() {
    try {
        System.out.println("Start processing client submission files, start at:" + Instant.now().toString());
        Stream<Path> submissionFiles = Files.walk(Paths.get(
"./client-submission"));
        Thread.sleep(1000);
// Mock processing time
       
// process files
        System.out.println(
"Complete processing client submission files, ended at:" + Instant.now().toString());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

fixedRate 和 FixedDelay 的区别。
当我们使用fixedRate毫秒数时,我们为此属性设置的是上一次开始和下一次开始之间的时间段。
另一方面,fixedDelay从前一次执行完成开始计数。
 
从属性加载调度配置
在这两个示例中,我们从字面上指定了源代码上的配置。有一个问题,如果我们想改变这些值,我们必须重新编译我们的项目。这不是一个好主意。
现在我们应该使用 Spring EL 从配置文件中加载这些值。动手吧:

@Scheduled(cron = "${scheduling.greet}")
public void greet() throws InterruptedException {
    System.out.println(
"Hi. Greeting from scheduled task." + Instant.now().toString());
}

# application.properties 
schedule.greet=0 0/1 * * * ?

通过这种方式,我们每次更改配置时,都不必重新编译项目,只需更改配置并重新部署即可。
 
以编程方式调度
这种进行调度的方法为您提供了更多控制权,您可以通过 if-else、循环等来控制项目如何执行计划任务。还有这种控制 ScheduledThreadPool 的能力。
那么如何做到这一点。我们可以通过创建一个实现org.springframework.scheduling.annotation.SchedulingConfigurer接口的配置类来做到这一点。

package com.programmingsharing.springboottopics.conf;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

@Configuration
@EnableScheduling
public class SchedulingConfiguration implements SchedulingConfigurer {

    @Value("${scheduling.greet}")
    private String greetCronExpression;
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addCronTask(greet(), greetCronExpression);
    }

    @Bean
    public Runnable greet() {
        return () -> System.out.println(
"Hello, this message come from scheduled tasks");
    }
}
 

概括
通过阅读到本文的最后,我们从使用一组注解EnableScheduling、Scheduled、Configuration的简单方式介绍了如何调度方法。展示cron、fixedRate、fixedDelay、initialDelay的用法和区别。我们还介绍了如何使用 Spring 表达式语言 (SpEL) 将调度外部化,并介绍了如何通过SchedulingConfigurer界面获得更多控制。