Spring Boot Scheduler调度器介绍

x33g5p2x  于2022-09-17 转载在 Spring  
字(10.5k)|赞(0)|评价(0)|浏览(396)

在这个Spring Boot的教程中,我们将看看Spring Boot调度器***。我们将看到如何用Spring Boot调度任务。在这篇文章中,让我们看看Spring @Scheduled注释*。

介绍

Spring Boot使用**@Scheduled注解**来安排任务。它在内部使用TaskScheduler接口来调度注释方法的执行。在使用该注解时,我们可能需要遵循某些规则。

  1. Methos不应该接受任何参数。
  2. 方法的返回类型应该是void。

1. 项目设置

让我们为我们的***Spring Boot调度器创建一个简单的应用程序。我们有以下选项来创建一个Spring Boot项目。

  1. 使用Spring Initializr
  2. IDE创建项目结构

这就是我们的pom.xml的样子。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.9.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.javadevjournal</groupId>
	<artifactId>spring-boot-scheduler</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-boot-scheduler</name>
	<description>Spring Boot schedule sample application</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

2. 启用调度 

启用调度;我们需要添加@EnableScheduling注解。我们有以下2个选项可以在我们的应用程序中添加这个注释。

  1. 在主类中添加@EnableScheduling注解。
  2. 用这个注解来注解配置类。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class SpringBootSchedulerApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringBootSchedulerApplication.class, args);
	}
}

@EnableScheduling注解创建了一个后台任务执行器。

3. 调度任务

任何调度器的主要工作是调度任务。Spring Boot使创建一个调度任务变得很容易。我们只需要用**@Scheduled注解来注解方法。让我们看一下其中一个例子,以便更好地理解。 

@Component
public class SayHelloTask {

    private static final Logger LOG = LoggerFactory.getLogger(SayHelloTask.class);

    @Scheduled(fixedRate = 1000)
    public void sayHello(){
        LOG.info("Hello from our simple scheduled method");
    }
}

让我们看一下一些重要的要点。

  1. @Scheduled注解定义了调度(例如,方法何时运行等)。
  2. 我们可以向注解传递一些参数来定制行为。

如果我们运行这个应用程序,在应用程序启动后,你会在控制台看到以下输出。

2019-10-10 20:53:12.447  INFO 45786 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method
2019-10-10 20:53:13.448  INFO 45786 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method
2019-10-10 20:53:14.446  INFO 45786 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method
2019-10-10 20:53:15.450  INFO 45786 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method
2019-10-10 20:53:16.448  INFO 45786 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method
2019-10-10 20:53:17.446  INFO 45786 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method

在下一节中,我们将看到一些可以与Scheduled注解一起使用的参数。

4. 具有固定速率的任务

要在一个固定的内部安排一个方法触发器,我们可以使用fixedRate注解中的@Scheduled参数。让我们举个例子,我们想每隔1秒执行一次方法。

@Scheduled(fixedRate = 1000)
    public void sayHello(){
        LOG.info("Hello from our simple scheduled method");
}

5.  用固定的延迟进行调度

假设我们希望在最后一次执行和下一次执行的开始之间有一个固定的延迟。我们可以在这个注释中使用fixedDelay参数。这个参数计算的是最后一次调用完成后的延迟。

@Scheduled(fixedDelay = 2000)
public void fixedDelayExample(){
  LOG.info("Hello from our Fixed delay method");
}

这就是输出的样子。

2019-10-10 21:19:38.331  INFO 46159 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our Fixed delay method
2019-10-10 21:19:40.333  INFO 46159 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our Fixed delay method
2019-10-10 21:19:42.345  INFO 46159 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our Fixed delay method
2019-10-10 21:19:44.346  INFO 46159 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our Fixed delay method

这里,任务被触发的延迟时间为2秒。为了更好地理解,让我们给我们的方法添加一些变化。让我们假设,我们的任务需要3分钟来完成,在这种情况下,下一次执行应该在5秒内开始(3秒完成,2秒延迟)。

@Scheduled(fixedDelay = 2000)
public void fixedDelayExample() {
    LOG.info("Hello from our Fixed delay method");
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException ie) {
        LOG.error("Got Interrupted {}", ie);
    }
}

当我们运行这段代码时,我们将有以下输出。

2019-10-10 21:25:11.623  INFO 46242 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our Fixed delay method
2019-10-10 21:25:16.629  INFO 46242 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our Fixed delay method
2019-10-10 21:25:21.633  INFO 46242 --- [  scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our Fixed delay method

总的延迟为5秒。

5.1.  平行调度

我们也可以通过在计划任务中添加@Async注解来启用并行调度。让我们看一下这个例子。

@EnableAsync
public class ParallelSchedulingExample {
    @Async
    @Scheduled(fixedDelay = 2000)
    public void fixedDelayExample() {
        LOG.info("Hello from our Fixed delay method");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException ie) {
            LOG.error("Got Interrupted {}", ie);
        }
    }
}

6. 安排一个具有初始延迟的任务

我们也可以使用initialDelay参数来延迟任务的第一次执行,延迟的时间为指定的毫秒。我们可以根据我们的要求将这个参数与fixedRatefixedDelay结合起来。

@Scheduled(fixedRate = 2000, initialDelay = 5000)
public void scheduleTaskWithInitialDelay() {
    LOG.info("Fixed Rate Task with Initial Delay");
}

@Scheduled(fixedRate = 2000, fixedDelay = 5000)
public void scheduleTaskWithInitialDelay() {
    LOG.info("Fixed Rate Task with Initial Delay");
}

当我们运行我们的应用程序时,我们将有以下输出。

2019-10-10 21:58:01.415  INFO 46959 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Fixed Rate Task with Initial Delay
2019-10-10 21:58:03.412  INFO 46959 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Fixed Rate Task with Initial Delay
2019-10-10 21:58:05.417  INFO 46959 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Fixed Rate Task with Initial Delay
2019-10-10 21:58:07.415  INFO 46959 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Fixed Rate Task with Initial Delay

7. Cron表达式

Cron表达式是一种灵活而强大的安排任务的方式。该模式是一个由六个单空格分隔的字段组成的列表:代表秒、分、小时、日、月、工作日。月和工作日的名称可以用英文名称的前三个字母来表示。在这个例子中,我们使用cron表达式为每1分钟调度一次任务。

@Scheduled(cron = "0 * * * * ?")
 public void scheduleTaskWithCronExpression() {
     LOG.info("Example to show how cron expression can be used");
 }

当我们运行我们的应用程序时,它将在每1分钟内执行任务。这就是输出结果的模样。

2019-10-12 19:25:00.003  INFO 74830 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Example to show how cron expression can be used
2019-10-12 19:26:00.003  INFO 74830 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Example to show how cron expression can be used

8. 参数化时间表

Spring Boot和Spring提供了一个强大的机制,可以使用属性文件将你的配置外部化。在开发任何企业应用程序时,将配置外部化总是很好的做法,以避免硬编码。它还有助于以下方面的工作

  1. 能够在不重新部署的情况下改变配置。
  2. 系统不需要为属性变化而重新编译。

让我们使用Spring表达式语言,通过属性文件将日程表表达式外部化,这就是新代码的样子。

@Scheduled($ {
    fixedrate.value
})
public void sayHello() {
    LOG.info("Hello from our simple scheduled method");
}

@Scheduled($ {
    fixeddelay.value
})
public void fixedDelayExample() {
    LOG.info("Hello from our Fixed delay method");
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException ie) {
        LOG.error("Got Interrupted {}", ie);
    }
}

@Scheduled($ {
    cron.expression
})
public void scheduleTaskWithCronExpression() {
    LOG.info("Example to show how cron expression can be used");
}

9. 自定义线程池

@Scheduled注解将执行默认线程池中的任务。Spring在启动时创建了大小为1的默认线程池。让我们运行之前的例子来验证这一声明。

2019-10-13 11:23:13.224  INFO 88646 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: scheduling-1
2019-10-13 11:23:14.225  INFO 88646 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: scheduling-1
2019-10-13 11:23:15.225  INFO 88646 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: scheduling-1
2019-10-13 11:23:16.225  INFO 88646 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: scheduling-1
2019-10-13 11:23:17.224  INFO 88646 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: scheduling-1
2019-10-13 11:23:18.221  INFO 88646 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: scheduling-1
2019-10-13 11:23:19.225  INFO 88646 --- [   scheduling-1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: scheduling-1

我们在日志中添加了以下一行来打印线程名称:Thread.currentThread().getName()。Spring提供了创建自定义线程池的灵活性,并使用自定义线程池执行所有任务。让我们看看如何为我们的应用程序创建和配置自定义线程池。 

@Configuration
public class CustomThreadPoolConfig implements SchedulingConfigurer {

    private final int CUSTOM_POOL_SIZE = 5;

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {

        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(CUSTOM_POOL_SIZE);
        threadPoolTaskScheduler.setThreadNamePrefix("javadevjournal");
        threadPoolTaskScheduler.initialize();

        //let's register our custom thread pool scheduler
        scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
    }
}

现在,当我们运行我们的应用程序时,Spring将使用新的线程池,这就是输出的样子。

2019-10-13 11:32:54.570  INFO 88821 --- [javadevjournal1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: javadevjournal1
2019-10-13 11:32:55.571  INFO 88821 --- [javadevjournal2] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: javadevjournal2
2019-10-13 11:32:56.571  INFO 88821 --- [javadevjournal1] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: javadevjournal1
2019-10-13 11:32:57.571  INFO 88821 --- [javadevjournal3] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: javadevjournal3
2019-10-13 11:32:58.572  INFO 88821 --- [javadevjournal3] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: javadevjournal3
2019-10-13 11:32:59.571  INFO 88821 --- [javadevjournal3] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: javadevjournal3
2019-10-13 11:33:00.569  INFO 88821 --- [javadevjournal3] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: javadevjournal3
2019-10-13 11:33:01.572  INFO 88821 --- [javadevjournal3] c.j.schedule.task.SayHelloTask           : Hello from our simple scheduled method, current thread is :: javadevjournal3</code?

对于简单的用例来说,Spring调度是一个不错的选择,但如果你正在寻找更高级的调度框架(比如持久化等),可以考虑使用Quartz调度器。

###总结

在这篇文章中,我们研究了Spring boot scheduler***。我们了解了配置和使用@Scheduled*注解的方法。我们看到了通过传递不同的参数来定制@Scheduled注释的不同选项。在这篇文章的最后,我们看到了如何为我们的应用程序配置自定义线程池。这篇文章的源代码可以在GitHub上找到。

相关文章