The @Scheduled Annotation in Spring – Spring中的@Scheduled注解

最后修改: 2014年 10月 18日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll illustrate how the Spring @Scheduled annotation can be used to configure and schedule tasks.

在本教程中,我们将说明Spring的@Scheduled注解如何用于配置和安排任务。

The simple rules that we need to follow to annotate a method with @Scheduled are:

我们用@Scheduled来注释一个方法需要遵循的简单规则是。

  • the method should typically have a void return type (if not, the returned value will be ignored)
  • the method should not expect any parameters

2. Enable Support for Scheduling

2.启用对调度的支持

To enable support for scheduling tasks and the @Scheduled annotation in Spring, we can use the Java enable-style annotation:

为了在Spring中启用对调度任务和@Scheduled注解的支持,我们可以使用Java enable-style注解。

@Configuration
@EnableScheduling
public class SpringConfig {
    ...
}

Conversely, we can do the same in XML:

反之,我们可以在XML中做同样的事情。

<task:annotation-driven>

3. Schedule a Task at Fixed Delay

3.按固定延迟安排任务

Let’s start by configuring a task to run after a fixed delay:

让我们先配置一个任务,让它在一个固定的延迟后运行。

@Scheduled(fixedDelay = 1000)
public void scheduleFixedDelayTask() {
    System.out.println(
      "Fixed delay task - " + System.currentTimeMillis() / 1000);
}

In this case, the duration between the end of the last execution and the start of the next execution is fixed. The task always waits until the previous one is finished.

在这种情况下,上一次执行结束和下一次执行开始之间的时间是固定的。该任务总是等待,直到前一个任务完成。

This option should be used when it’s mandatory that the previous execution is completed before running again.

当强制要求在再次运行前完成前一次的执行时,应该使用这个选项。

4. Schedule a Task at a Fixed Rate

4.按固定费率安排任务

Let’s now execute a task at a fixed interval of time:

现在让我们在一个固定的时间间隔内执行一个任务。

@Scheduled(fixedRate = 1000)
public void scheduleFixedRateTask() {
    System.out.println(
      "Fixed rate task - " + System.currentTimeMillis() / 1000);
}

This option should be used when each execution of the task is independent.

当每个任务的执行都是独立的时候,应该使用这个选项。

Note that scheduled tasks don’t run in parallel by default. So even if we used fixedRate, the next task won’t be invoked until the previous one is done.

请注意,计划任务在默认情况下是不平行运行的。因此,即使我们使用fixedRate,在前一个任务完成之前,下一个任务也不会被调用。

If we want to support parallel behavior in scheduled tasks, we need to add the @Async annotation:

如果我们想在计划任务中支持并行行为,我们需要添加@Async注解:

@EnableAsync
public class ScheduledFixedRateExample {
    @Async
    @Scheduled(fixedRate = 1000)
    public void scheduleFixedRateTaskAsync() throws InterruptedException {
        System.out.println(
          "Fixed rate task async - " + System.currentTimeMillis() / 1000);
        Thread.sleep(2000);
    }

}

Now this asynchronous task will be invoked each second, even if the previous task isn’t done.

现在这个异步任务每秒都会被调用,即使前一个任务还没有完成。

5. Fixed Rate vs Fixed Delay

5.固定利率与固定延时

We can run a scheduled task using Spring’s @Scheduled annotation, but based on the properties fixedDelay and fixedRate, the nature of execution changes.

我们可以使用Spring的@Scheduled注解来运行一个计划任务,但基于fixedDelayfixedRate>属性,执行的性质发生了变化。

The fixedDelay property makes sure that there is a delay of n millisecond between the finish time of an execution of a task and the start time of the next execution of the task.

fixedDelay属性确保在一个任务的执行结束时间和下一个任务的执行开始时间之间有一个n毫秒的延迟。

This property is specifically useful when we need to make sure that only one instance of the task runs all the time. For dependent jobs, it is quite helpful.

当我们需要确保一直只有一个任务的实例在运行时,这个属性特别有用。对于依赖性工作,它是相当有帮助的。

The fixedRate property runs the scheduled task at every n millisecond. It doesn’t check for any previous executions of the task.

fixedRate属性每隔n毫秒运行一次计划任务。它不检查任务的任何先前执行。

This is useful when all executions of the task are independent. If we don’t expect to exceed the size of the memory and the thread pool, fixedRate should be quite handy.

当任务的所有执行都是独立的时候,这很有用。如果我们不期望超过内存和线程池的大小,fixedRate应该是相当方便的。

Although, if the incoming tasks do not finish quickly, it’s possible they end up with “Out of Memory exception”.

虽然,如果传入的任务没有迅速完成,它们有可能以 “内存不足的异常 “而告终。

6. Schedule a Task With Initial Delay

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

Next, let’s schedule a task with a delay (in milliseconds):

接下来,让我们安排一个有延迟(以毫秒为单位)的任务。

@Scheduled(fixedDelay = 1000, initialDelay = 1000)
public void scheduleFixedRateWithInitialDelayTask() {
 
    long now = System.currentTimeMillis() / 1000;
    System.out.println(
      "Fixed rate task with one second initial delay - " + now);
}

Note how we’re using both fixedDelay as well as initialDelay in this example. The task will be executed the first time after the initialDelay value, and it will continue to be executed according to the fixedDelay.

注意我们在这个例子中同时使用了fixedDelay以及initialDelay。任务将在initialDelay值之后第一次被执行,并将继续根据fixedDelay执行。

This option is convenient when the task has a setup that needs to be completed.

当任务有一个需要完成的设置时,这个选项很方便。

7. Schedule a Task Using Cron Expressions

7.使用Cron表达式安排任务

Sometimes delays and rates are not enough, and we need the flexibility of a cron expression to control the schedule of our tasks:

有时延迟和速率是不够的,我们需要cron表达式的灵活性来控制我们任务的时间表。

@Scheduled(cron = "0 15 10 15 * ?")
public void scheduleTaskUsingCronExpression() {
 
    long now = System.currentTimeMillis() / 1000;
    System.out.println(
      "schedule tasks using cron jobs - " + now);
}

Note that in this example, we’re scheduling a task to be executed at 10:15 AM on the 15th day of every month.

请注意,在这个例子中,我们把一个任务安排在每个月的第15天上午10:15执行。

By default, Spring will use the server’s local time zone for the cron expression. However, we can use the zone attribute to change this timezone:

默认情况下,Spring将使用服务器的本地时区来表达cron。然而,我们可以使用zone属性来改变这个时区

@Scheduled(cron = "0 15 10 15 * ?", zone = "Europe/Paris")

With this configuration, Spring will schedule the annotated method to run at 10:15 AM on the 15th day of every month in Paris time.

通过这种配置,Spring将安排注释的方法在巴黎时间每月15日上午10:15运行。

8. Parameterizing the Schedule

8.时间表的参数化

Hardcoding these schedules is simple, but we usually need to be able to control the schedule without re-compiling and re-deploying the entire app.

硬编码这些时间表很简单,但我们通常需要能够控制时间表而不需要重新编译和重新部署整个应用程序。

We’ll make use of Spring Expressions to externalize the configuration of the tasks, and we’ll store these in properties files.

我们将利用Spring表达式来外部化任务的配置,并将这些配置存储在属性文件中。

A fixedDelay task:

一个fixedDelay任务。

@Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds}")

A fixedRate task:

一个fixedRate任务。

@Scheduled(fixedRateString = "${fixedRate.in.milliseconds}")

A cron expression based task:

一个基于cron表达式的任务。

@Scheduled(cron = "${cron.expression}")

9. Configuring Scheduled Tasks Using XML

9.使用XML配置预定任务

Spring also provides an XML way of configuring the scheduled tasks. Here is the XML configuration to set these up:

Spring还提供了一种XML方式来配置计划任务。下面是设置这些任务的XML配置。

<!-- Configure the scheduler -->
<task:scheduler id="myScheduler" pool-size="10" />

<!-- Configure parameters -->
<task:scheduled-tasks scheduler="myScheduler">
    <task:scheduled ref="beanA" method="methodA" 
      fixed-delay="5000" initial-delay="1000" />
    <task:scheduled ref="beanB" method="methodB" 
      fixed-rate="5000" />
    <task:scheduled ref="beanC" method="methodC" 
      cron="*/5 * * * * MON-FRI" />
</task:scheduled-tasks>

10. Setting Delay or Rate Dynamically at Runtime

10.在运行时动态地设置延迟或速率

Normally, all the properties of the @Scheduled annotation are resolved and initialized only once at Spring context startup.

通常,@Scheduled注解的所有属性只在Spring上下文启动时被解析和初始化一次。

Therefore, changing the fixedDelay or fixedRate values at runtime isn’t possible when we use @Scheduled annotation in Spring.

因此,当我们在Spring中使用@Scheduled注解时,在运行时改变fixedDelayfixedRate值是不可能的

However, there is a workaround. Using Spring’s SchedulingConfigurer provides a more customizable way to give us the opportunity of setting the delay or rate dynamically.

然而,有一个变通方法。使用Spring的SchedulingConfigurer提供了一种更可定制的方式,使我们有机会动态地设置延迟或速率

Let’s create a Spring configuration, DynamicSchedulingConfig, and implement the SchedulingConfigurer interface:

让我们创建一个Spring配置,DynamicSchedulingConfig,并实现SchedulingConfigurer接口。

@Configuration
@EnableScheduling
public class DynamicSchedulingConfig implements SchedulingConfigurer {

    @Autowired
    private TickService tickService;

    @Bean
    public Executor taskExecutor() {
        return Executors.newSingleThreadScheduledExecutor();
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
        taskRegistrar.addTriggerTask(
          new Runnable() {
              @Override
              public void run() {
                  tickService.tick();
              }
          },
          new Trigger() {
              @Override
              public Date nextExecutionTime(TriggerContext context) {
                  Optional<Date> lastCompletionTime =
                    Optional.ofNullable(context.lastCompletionTime());
                  Instant nextExecutionTime =
                    lastCompletionTime.orElseGet(Date::new).toInstant()
                      .plusMillis(tickService.getDelay());
                  return Date.from(nextExecutionTime);
              }
          }
        );
    }

}

As we notice, with the help of the ScheduledTaskRegistrar#addTriggerTask method, we can add a Runnable task and a Trigger implementation to recalculate the nextExecutionTime after the end of each execution.

正如我们注意到的,在ScheduledTaskRegistrar#addTriggerTask方法的帮助下,我们可以添加一个Runnable任务和一个Trigger实现,在每次执行结束后重新计算nextExecutionTime

Additionally, we annotate our DynamicSchedulingConfig with @EnableScheduling to make the scheduling work.

此外,我们用@EnableScheduling来注释我们的DynamicSchedulingConfig,以使调度发挥作用。

As a result, we scheduled the TickService#tick method to run it after each amount of delay, which is determined dynamically at runtime by the getDelay method.

因此,我们安排TickService#tick方法在每个延迟量之后运行它,这个延迟量在运行时由getDelay方法动态确定。

11. Running Tasks in Parallel

11.平行运行任务

By default, Spring uses a local single-threaded scheduler to run the tasks. As a result, even if we have multiple @Scheduled methods, they each need to wait for the thread to complete executing a previous task.

默认情况下,Spring使用本地单线程调度器来运行任务。因此,即使我们有多个@Scheduled方法,它们都需要等待线程完成对前一个任务的执行。

If our tasks are truly independent, it’s more convenient to run them in parallel. For that, we need to provide a TaskScheduler that better suits our needs:

如果我们的任务是真正独立的,那么并行运行它们会更方便。为此,我们需要提供一个TaskScheduler,更好地满足我们的需求。

@Bean
public TaskScheduler  taskScheduler() {
    ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(5);
    threadPoolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
    return threadPoolTaskScheduler;
}

In the above example, we configured the TaskScheduler with a pool size of five, but keep in mind that the actual configuration should be fine-tuned to one’s specific needs.

在上面的例子中,我们将TaskScheduler的池子大小配置为5,但请记住,实际配置应该根据自己的具体需求进行微调。

11.1. Using Spring Boot

11.1.使用Spring Boot

If we use Spring Boot, we can make use of an even more convenient approach to increase the scheduler’s pool size.

如果我们使用Spring Boot,我们可以利用一个更方便的方法来增加调度器的池大小。

It’s simply enough to set the spring.task.scheduling.pool.size property:
spring.task.scheduling.pool.size=5

只需设置spring.task.scheduling.pool.size属性即可:
spring.task.scheduling.pool.size=5

12. Conclusion

12.结论

In this article, we discussed the way to configure and use the @Scheduled annotation.

在这篇文章中,我们讨论了配置和使用@Scheduled注释的方法

We covered the process to enable scheduling, and various ways of configuring scheduling task patterns. We also showed a workaround to configure the delay and rate dynamically.

我们涵盖了启用调度的过程,以及配置调度任务模式的各种方法。我们还展示了一个动态配置延迟和速率的工作方法。

The examples shown above can be found over on GitHub.

上面显示的例子可以在GitHub上找到over