Статьи

Работать вовремя с запланированными задачами Spring

Вам нужно запускать процесс каждый день в одно и то же время, как будильник? Тогда запланированные задачи Spring для вас. Позволяет вам аннотировать метод с помощью @Scheduled заставляя его запускаться в определенное время или через определенный промежуток времени. В этом посте мы рассмотрим настройку проекта, который может использовать запланированные задачи, а также как использовать различные методы для определения того, когда они выполняются.

Я буду использовать Spring Boot для этого поста, чтобы сделать зависимости красивыми и простыми из-за того, что планирование доступно для зависимости spring-boot-starter которая будет в некотором роде включена в каждый проект Spring Boot. Это позволяет вам использовать любые другие зависимости для стартера, так как они будут задействовать spring-boot-starter и все его отношения. Если вы хотите включить саму точную зависимость, используйте spring-context .

Вы можете использовать spring-boot-starter .

1
2
3
4
5
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>2.0.0.RC1</version>
</dependency>

Или используйте spring-context напрямую.

1
2
3
4
5
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.0.3.RELEASE</version>
</dependency>

Создать запланированное задание довольно просто. Добавьте аннотацию @Scheduled для любого метода, который вы хотите запустить автоматически, и @EnableScheduling в файл конфигурации.

Так, например, вы можете иметь что-то вроде ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@Component
public class EventCreator {
 
  private static final Logger LOG = LoggerFactory.getLogger(EventCreator.class);
 
  private final EventRepository eventRepository;
 
  public EventCreator(final EventRepository eventRepository) {
    this.eventRepository = eventRepository;
  }
 
  @Scheduled(fixedRate = 1000)
  public void create() {
    final LocalDateTime start = LocalDateTime.now();
    eventRepository.save(
        new Event(new EventKey("An event type", start, UUID.randomUUID()), Math.random() * 1000));
    LOG.debug("Event created!");
  }
}

Здесь довольно много кода, который не имеет значения для запуска запланированного задания. Как я сказал минуту назад, нам нужно использовать @Scheduled для метода, и он запустится автоматически. Таким образом, в приведенном выше примере метод create будет запускаться каждые 1000 мс (1 секунда), как fixedRate свойстве fixedRate аннотации. Если мы хотим изменить частоту его выполнения, мы можем увеличить или уменьшить время fixedRate или рассмотреть возможность использования различных доступных нам методов планирования.

Таким образом, вы, вероятно, хотите знать, каковы эти другие способы? Ну, вот они (я также fixedRate здесь fixedRate ).

  • fixedRate выполняет метод с фиксированным периодом в миллисекундах между вызовами.
  • fixedRateString такой же, как fixedRate но со строковым значением.
  • fixedDelay выполняет метод с фиксированным периодом в миллисекунды между концом одного вызова и началом следующего.
  • fixedDelayString такой же, как fixedDelay но со строковым значением.
  • cron использует cron-подобные выражения, чтобы определить, когда выполнять метод (мы рассмотрим это более подробно позже).

Есть несколько других служебных свойств, доступных для аннотации @Scheduled .

  • zone Указывает часовой пояс, для которого будет разрешено выражение cron. Если часовой пояс не указан, будет использоваться часовой пояс сервера по умолчанию. Поэтому, если вам нужно, чтобы он работал в определенном часовом поясе, например, в Гонконге, вы можете использовать zone = "GMT+8:00" .
  • initialDelay Количество миллисекунд, чтобы задержать первое выполнение запланированной задачи, требует использования одного из свойств фиксированной скорости или фиксированной задержки.
  • initialDelayString То же, что initialDelay но со строковым значением.

Несколько примеров использования фиксированных ставок и задержек можно найти ниже.

1
@Scheduled(fixedRate = 1000)

То же, что и раньше, запускается каждую 1 секунду.

1
@Scheduled(fixedRateString = "1000")

То же, что и выше.

1
@Scheduled(fixedDelay = 1000)

Запускается через 1 секунду после завершения предыдущего вызова.

1
@Scheduled(fixedRate = 1000, initialDelay = 5000)

Запускается каждую секунду, но ждет 5 секунд, прежде чем выполнится в первый раз.

Теперь рассмотрим свойство cron которое дает гораздо больший контроль над расписанием задачи, позволяя нам определить секунды, минуты и часы, в которые выполняется задача, но может пойти еще дальше и указать даже годы, в которые задача будет выполняться.

Ниже приведена разбивка компонентов, которые создают выражение cron.

  • Seconds могут иметь значения 0-59 или специальные символы , - * / .
  • Minutes могут иметь значения 0-59 или специальные символы , - * / .
  • Hours могут иметь значения 0-59 или специальные символы , - * / .
  • Day of month может иметь значения 1-31 или специальные символы , - * ? / LWC , - * ? / LWC .
  • Month может иметь значения 1-12 , JAN-DEC или специальные символы , - * / .
  • Day of week может иметь значения 1-7 , SUN-SAT или специальные символы , - * ? / LC # , - * ? / LC # .
  • Year может быть пустым, иметь значения 1970-2099 или специальные символы , - * / .

Просто для большей ясности я объединил разбивку в выражение, состоящее из меток полей.

1
@Scheduled(cron = "[Seconds] [Minutes] [Hours] [Day of month] [Month] [Day of week] [Year]")

Пожалуйста, не включайте фигурные скобки в свои выражения (я использовал их, чтобы сделать выражение более понятным).

Прежде чем мы продолжим, нам нужно разобраться, что означают специальные символы.

  • * представляет все значения, поэтому, если используется во втором поле, оно означает каждую секунду или используется в поле дня, означая запускать каждый день.
  • ? не представляет никакого конкретного значения и может использоваться в поле дня месяца или дня недели, где использование одного делает недействительным другое. Если мы укажем срабатывание 15 числа месяца, то ? будет использоваться в поле « Day of week .
  • - представляет инклюзивный диапазон значений, например 1-3 в поле часов означает часы 1, 2 и 3.
  • , представляет дополнительные значения, например, MON, WED, SUN в полях дня недели, означает понедельник, среду и воскресенье.
  • / представляет приращения, например, 0/15 в поле секунд запускается каждые 15 секунд, начиная с 0 (0, 15, 30 и 45).
  • L представляет последний день недели или месяца. Помните, что в этом контексте суббота является концом недели, поэтому использование L в поле дня недели вызовет субботу. Это может использоваться в сочетании с числом в поле дня месяца, таким как 6L для представления последней пятницы месяца или выражением типа L-3 обозначающим третий с последнего дня месяца. Если мы указываем значение в поле дня недели, которое мы должны использовать ? в поле дня месяца, и наоборот.
  • W представляет ближайший день недели месяца. Например, если 15W сработает в 15-й день месяца, если это будний день, в противном случае он будет работать в ближайший день недели. Это значение нельзя использовать в списке значений дня.
  • # задает как день недели, так и неделю, когда должно запускаться задание. Например, 5#2 означает второй четверг месяца. Если указанные вами день и неделя переполнятся на следующий месяц, они не сработают.

Здесь можно найти полезный ресурс с чуть более длинными пояснениями, который помог мне написать этот пост.

Давайте рассмотрим несколько примеров.

1
@Scheduled(cron = "0 0 12 * * ?")

Пожары в 12 вечера каждый день.

1
@Scheduled(cron = "0 15 10 * * ? 2005")

Пожары в 10:15 каждый день в 2005 году.

1
@Scheduled(cron = "0/20 * * * * ?")

Срабатывает каждые 20 секунд.

Для некоторых других примеров см. Ссылку, которую я упоминал ранее, показанную здесь К счастью, если вы застряли при написании простого выражения cron, вы сможете найти нужный вам сценарий, поскольку кто-то, возможно, уже задавал тот же вопрос о переполнении стека.

Чтобы связать некоторые из вышеперечисленных в небольшой пример кода, см. Код ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class AverageMonitor {
 
  private static final Logger LOG = LoggerFactory.getLogger(AverageMonitor.class);
  private final EventRepository eventRepository;
  private final AverageRepository averageRepository;
 
  public AverageMonitor(
      final EventRepository eventRepository, final AverageRepository averageRepository) {
    this.eventRepository = eventRepository;
    this.averageRepository = averageRepository;
  }
 
  @Scheduled(cron = "0/20 * * * * ?")
  public void publish() {
    final double average =
        eventRepository.getAverageValueGreaterThanStartTime(
            "An event type", LocalDateTime.now().minusSeconds(20));
    averageRepository.save(
        new Average(new AverageKey("An event type", LocalDateTime.now()), average));
    LOG.info("Average value is {}", average);
  }
}

Здесь у нас есть класс, который запрашивает Cassandra каждые 20 секунд для среднего значения событий за тот же период времени. Опять же, большая часть кода здесь является шумом от аннотации @Scheduled но может быть полезно увидеть ее в дикой природе. Более того, если вы были внимательны, для этого fixedRate использования запуска каждые 20 секунд было бы целесообразно использовать fixedRate и, возможно, свойства fixedDelay вместо cron , поскольку мы выполняем задачу так часто.

1
@Scheduled(fixedRate = 20000)

Является ли fixedRate эквивалентом выражения cron, использованного выше.

Последнее требование, о котором я упоминал ранее, — добавить аннотацию @EnableScheduling в класс конфигурации.

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableScheduling
public class Application {
 
  public static void main(final String args[]) {
    SpringApplication.run(Application.class);
  }
}

Поскольку это небольшое приложение Spring Boot, я прикрепил аннотацию @EnableScheduling к основному классу @SpringBootApplication .

В заключение, мы можем запланировать запуск задач с помощью аннотации @Scheduled а также либо с @Scheduled до миллисекунды между выполнениями, либо с помощью выражения cron для более точного времени, которое нельзя выразить первым. Для задач, которые нужно запускать очень часто, достаточно использовать свойства fixedRate или fixedDelay , но как только время между выполнениями станет больше, будет сложнее быстро определить определенное время. Когда это происходит, следует использовать свойство cron для большей ясности запланированного времени.

Небольшое количество кода, используемого в этом посте, можно найти на моем GitHub .

Если вы сочли этот пост полезным и хотите быть в курсе моих новых учебных пособий по мере их написания, следите за мной в Twitter по адресу @LankyDanDev .

Опубликовано на Java Code Geeks с разрешения Дэна Ньютона, партнера нашей программы JCG . См. Оригинальную статью здесь: Запуск в срок с запланированными задачами Spring

Мнения, высказанные участниками Java Code Geeks, являются их собственными.