SpringBoot использует аннотацию @Scheduled для реализации задач синхронизации

Предисловие

После версии Spring 3.0 поставляется с синхронизированными задачами, а аннотации @EnableScheduling и @Scheduled предоставляются для реализации функций синхронизированных задач.

Использовать SpringBoot для создания задачи по времени очень просто. В настоящее время существует три основных способа ее создания:

  • 1. На основе аннотаций (@Scheduled)
  • 2. На основе интерфейса (SchedulingConfigurer) Первый, как полагают, знаком всем, но на практике мы часто хотим считывать указанное время из базы данных для динамического выполнения задач синхронизации. В настоящее время задачи синхронизации на основе интерфейса пригодиться.
  • 3. Установите многопоточные задачи синхронизации на основе аннотаций.

1. На основе аннотаций (@Scheduled)

1.1 Использование аннотации @Scheduled и @EnableScheduling

Основываясь на аннотации, @Scheduled по умолчанию является однопоточным.При запуске нескольких задач время выполнения задачи зависит от времени выполнения предыдущей задачи.

@EnableScheduling Annotation: Используйте в классе конфигурации, чтобы включить поддержку запланированных задач (в классе).

@Scheduled annotation: объявить, что это задача, включая cron, fixDelay, fixRate и другие типы (с точки зрения метода вам нужно сначала включить поддержку запланированных задач).

[Пример] Аннотации @Scheduled и @EnableScheduling используются в проекте SpringBoot для реализации задач синхронизации.

(1) Начать задачи по хронометражу

В проекте SpringBoot добавьте аннотацию @ EnableScheduling к классу запуска проекта, чтобы включить управление задачами по времени.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling //开启定时任务
public class ScheduledDemoApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(ScheduledDemoApplication.class, args);
    }
}

(2) Создавайте рассчитанные по времени задачи

Создайте запланированную задачу и используйте аннотацию @Scheduled.

package com.pjb.Schedule;

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

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 定时任务的使用
 * @author pan_junbiao
 **/
@Component
public class Task
{
    @Scheduled(cron="0/5 * *  * * ? ")   //每5秒执行一次
    public void execute(){
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //设置日期格式
        System.out.println("欢迎访问 pan_junbiao的博客 " + df.format(new Date()));
    }
}

Результаты:

1.2 Объяснение каждого параметра аннотации @Scheduled

Использование аннотации @Scheduled здесь не объясняется подробно, а 8 параметров объясняются напрямую.

Исходный код класса аннотации @Scheduled выглядит следующим образом:

package org.springframework.scheduling.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    String CRON_DISABLED = "-";

    String cron() default "";

    String zone() default "";

    long fixedDelay() default -1L;

    String fixedDelayString() default "";

    long fixedRate() default -1L;

    String fixedRateString() default "";

    long initialDelay() default -1L;

    String initialDelayString() default "";
}

1.2.1 cron

Этот параметр принимает выражение cron. Выражение cron представляет собой строку. Строка разделена 5 или 6 пробелами, разделенных в общей сложности 6 или 7 полями, и каждое поле представляет значение.

Синтаксис выражения Cron:

Формат: [секунда] [минута] [час] [день] [месяц] [неделя] [год]

Серийный номер Описание Это требуется Допустимые значения Разрешенные подстановочные знаки
1 второй да 0–59 , - * /
2 Минуты да 0–59 , - * /
3 час да 0–23 , - * /
4 день да 1-31 , - *? / LW
5 месяц да 1-12 или ЯНВ-ДЕКАБРЬ , - * /
6 неделю да 1-7 или ВС-СБ , - *? / L #
7 год нет пустой или 1970-2099 , - * /

Описание подстановочного знака:

Означает все значения. Например: установка «*» в поле минут означает, что он будет запускаться каждую минуту.

?  Означает, что значение не указано. Используемый сценарий заключается в том, что вам не нужно заботиться о текущем установленном значении этого поля. Например: вы хотите запускать операцию 10 числа каждого месяца, но не заботитесь о дне недели, поэтому вам нужно установить поле позиции недели на «?» В частности, установить его на 0 0 0 10 *?

-  представляющий интервал. Например, установка «10–12» на час означает, что он сработает в 10, 11 и 12 часов.

,  Означает указать несколько значений, например, установка "MON, WED, FRI" в поле недели означает запуск в понедельник, среду и пятницу.

/  Используется для инкрементного запуска. Например, установите «5/15» на секунду, это означает, что запускается с 5 секунд и запускается каждые 15 секунд (5, 20, 35, 50). Установите «1/3» в поле месяца, чтобы запускать 1 числа каждого месяца и запускать один раз каждые три дня.

L  означает последнее значение. В настройке поля дня это означает последний день месяца (в соответствии с текущим месяцем, если это февраль, это также будет зависеть от того, является ли этот год лунным [високосным]), а в поле недели это означает субботу. , что эквивалентно «7» или «SAT». Если вы добавляете число перед "L", это означает последний из данных. Например, установка формата «6L» в поле недели означает «последняя пятница месяца».

W  представляет собой рабочий день, ближайший к указанной дате (с понедельника по пятницу). Например, установка «15W» в поле дня означает, что триггер срабатывает в рабочий день, ближайший к 15-му числу каждого месяца. Если 15-е число выпадает на субботу, найдите ближайшую пятницу (14-е) для срабатывания, если 15-е - будний день, найдите ближайший следующий понедельник (16-е), чтобы сработать. Неделя) 5), он сработает в этот день. Если указан формат «1W», это означает, что он будет запускаться в ближайший рабочий день после 1-го числа каждого месяца. Если 1-й выпадет в субботу, он сработает 3-го числа следующего понедельника. (Учтите, что перед "W" можно установить только определенные числа, а интервал "-" не допускается).

Подсказки: «L» и «W» можно использовать вместе. Если в поле дня установлено «LW», это означает, что он будет активирован в последний рабочий день месяца (обычно это относится к расчету заработной платы).

#  Серийный номер (представляющий первые несколько недель месяца), например, установка "6 # 3" в поле недели означает, что это третья суббота каждого месяца. Обратите внимание, что если указано "# 5", там На пятой неделе нет недели Шесть, эта конфигурация не сработает (подходит для Дня матери и Дня отца)

Советы: При использовании английских букв настройка поля недели не чувствительна к регистру. MON то же самое, что и mon.

Вы можете использовать онлайн-инструмент для генерации выражений cron: http://cron.qqe2.com/  для генерации нужных вам выражений.

Общие примеры:

0 0 12 * *? Триггер в 12 часов каждый день
0 15 10? * * Срабатывание в 10:15 каждый день
0 15 10 * *? Срабатывание в 10:15 каждый день
0 15 10 * *? * Срабатывание в 10:15 каждый день
0 15 10 * *? 2005 г. Пожар в 10:15 каждый день в 2005 г.
0 * 14 * *? Запускать каждую минуту с 14:00 до 14:59 каждый день
0 0/5 14 * *? Каждый день с 14 до 14:59 (начинается в час и срабатывает каждые 5 минут)
0 0/5 14,18 * *? Каждый день с 14:00 до 14:59 (начинается в час и срабатывает каждые 5 минут) каждый день с 18 до 18:59 (начинается в час и срабатывает каждые 5 минут)
0 0-5 14 * *? Срабатывает каждую минуту с 14:00 до 14:05 каждый день
0 10,44 14? 3 СР Срабатывает каждую среду в 2:10 и 2:44 днем ​​в марте.
0 15 10? * ПН-ПТ Пожар в 10:15 каждый день с понедельника по пятницу
0 15 10 15 *? Пожар в 10:15 15 числа каждого месяца
0 15 10 л *? Запуск в 10:15 в последний день каждого месяца
0 15 10? * 6л Срабатывать в 10:15 в последнюю пятницу каждого месяца
0 15 10? * 6L 2002-2005 гг. Срабатывает в 10:15 в последнюю пятницу каждого месяца с 2002 по 2005 гг.
0 15 10? * 6 # 3 Запускается в пятницу третьей недели каждого месяца.
0 0 12 1/5 *? Срабатывает каждые 5 дней, начиная с первого полудня каждого месяца
0 11 11 11 11? Пожар в 11:11 11 ноября каждого года (День холостяков)

В выражениях cron используются заполнители

Кроме того, выражение cron, полученное атрибутом cron, поддерживает заполнители. например:

Конфигурационный файл:

time:
  cron: */5 * * * * *
  interval: 5

Выполнять каждые 5 секунд:

@Scheduled(cron="${time.cron}")
void testPlaceholder1() {
    System.out.println("Execute at " + System.currentTimeMillis());
}

@Scheduled(cron="*/${time.interval} * * * * *")
void testPlaceholder2() {
    System.out.println("Execute at " + System.currentTimeMillis());
}

1.2.2 зона

Часовой пояс, получите идентификатор java.util.TimeZone #. Выражение cron будет проанализировано в зависимости от часового пояса. По умолчанию это пустая строка, то есть часовой пояс, в котором расположен сервер. Например, мы обычно используем часовой пояс Asia / Shanghai. Обычно мы оставляем это поле пустым.

1.2.3 fixedDelay

Сколько времени потребуется для выполнения после последнего момента времени выполнения. Такие как:

@Scheduled(fixedDelay = 5000) //上一次执行完毕时间点之后5秒再执行

1.2.4 fixedDelayString

Он имеет то же значение, что и 1.2.3 fixedDelay, но использует форму строки. Единственная разница в том, что поддерживаются заполнители. Такие как:

@Scheduled(fixedDelayString = "5000") //上一次执行完毕时间点之后5秒再执行

Использование заполнителей:

Добавьте следующую конфигурацию в файл конфигурации application.yml:

time:
  fixedDelay: 5000

Напишите соответствующий код: 

/**
 * 定时任务的使用
 * @author pan_junbiao
 **/
@Component
public class Task
{
    @Scheduled(fixedDelayString = "${time.fixedDelay}")
    void testFixedDelayString()
    {
        System.out.println("欢迎访问 pan_junbiao的博客 " + System.currentTimeMillis());
    }
}

Результаты:

1.2.5 fixedRate

Сколько времени прошло после того, как в последний раз началась казнь. Такие как:

@Scheduled(fixedRate = 5000) //上一次开始执行时间点之后5秒再执行

1.2.6 fixedRateString

Он имеет то же значение, что и 1.2.5 fixedRate, но использует форму строки. Единственная разница в том, что поддерживаются заполнители.

1.2.7 initialDelay

Как долго откладывать казнь после первого раза. Такие как:

@Scheduled(initialDelay=1000, fixedRate=5000) //第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次

1.2.8 initialDelayString

Он имеет то же значение, что и 1.2.7 initialDelay, но использует форму строки. Единственная разница в том, что поддерживаются заполнители.

 

2. Динамический: на основе интерфейса (SchedulingConfigurer)

На основе интерфейса (SchedulingConfigurer).

(1) Создайте таблицу данных

Создайте таблицу cron в базе данных MySQL и добавьте данные.

DROP TABLE IF EXISTS cron;
CREATE TABLE cron  (
  cron_id VARCHAR(30) NOT NULL PRIMARY KEY,
  cron VARCHAR(30) NOT NULL  
);

INSERT INTO cron VALUES ('1', '0/5 * * * * ?');

(2) Добавьте информацию о конфигурации pom.xml

Добавьте зависимости драйвера базы данных JDBC для MyBatis и MySQL в файл конфигурации pom.xml.

<!-- MyBatis与SpringBoot整合依赖 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>

<!-- MySQL的JDBC数据库驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.20</version>
</dependency>

(3) Информация, связанная с конфигурацией

Измените суффикс файла application.properties проекта по умолчанию на «.yml», то есть имя файла конфигурации: application.yml, и настройте следующую информацию:

spring:
  #DataSource数据源
  datasource:
    url: jdbc:mysql://localhost:3306/db_admin?useSSL=false&amp
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

#MyBatis配置
mybatis:
  type-aliases-package: com.pjb.entity #别名定义
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #指定 MyBatis 所用日志的具体实现,未指定时将自动查找
    map-underscore-to-camel-case: true #开启自动驼峰命名规则(camel case)映射
    lazy-loading-enabled: true #开启延时加载开关
    aggressive-lazy-loading: false #将积极加载改为消极加载(即按需加载),默认值就是false
    lazy-load-trigger-methods: "" #阻挡不相干的操作触发,实现懒加载
    cache-enabled: true #打开全局缓存开关(二级环境),默认值就是true

(4) Создайте таймер

После того, как база данных подготовила данные, мы пишем задачу синхронизации. Обратите внимание, что здесь добавляется TriggerTask. Цель состоит в том, чтобы циклически считывать цикл выполнения, который мы установили в базе данных, и выполнять содержимое связанных задач синхронизации. Конкретный код выглядит следующим образом:

package com.pjb.config;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;

/**
 * 动态定时任务配置类
 * @author pan_junbiao
 **/
@Configuration      //1.主要用于标记配置类,兼备Component的效果
@EnableScheduling   //2.开启定时任务
public class DynamicScheduleConfigurer implements SchedulingConfigurer
{
    @Mapper
    public interface CronMapper {
        @Select("select cron from cron limit 1")
        public String getCron();
    }

    //注入mapper
    @Autowired
    @SuppressWarnings("all")
    CronMapper cronMapper;

    /**
     * 执行定时任务.
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar)
    {
        taskRegistrar.addTriggerTask(
                //1.添加任务内容(Runnable)
                () -> System.out.println("欢迎访问 pan_junbiao的博客: " + LocalDateTime.now().toLocalTime()),
                //2.设置执行周期(Trigger)
                triggerContext -> {
                    //2.1 从数据库获取执行周期
                    String cron = cronMapper.getCron();
                    //2.2 合法性校验.
                    if (StringUtils.isEmpty(cron)) {
                        // Omitted Code ..
                    }
                    //2.3 返回执行周期(Date)
                    return new CronTrigger(cron).nextExecutionTime(triggerContext);
                }
        );
    }
}

Результаты:

Примечание.  Если при изменении базы данных произошла ошибка в формате, выполнение задачи по времени будет остановлено, даже если модификация верна; в это время проект можно восстановить только путем перезапуска проекта.

 

3. Установите многопоточные задачи синхронизации на основе аннотаций.

Создавайте многопоточные задачи синхронизации.

package com.pjb.Task;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;

/**
 * 基于注解设定多线程定时任务
 * @author pan_junbiao
 */
@Component
@EnableScheduling   // 1.开启定时任务
@EnableAsync        // 2.开启多线程
public class MultithreadScheduleTask
{
    @Async
    @Scheduled(fixedDelay = 1000)  //间隔1秒
    public void first() throws InterruptedException {
        System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
        System.out.println();
        Thread.sleep(1000 * 10);
    }

    @Async
    @Scheduled(fixedDelay = 2000)
    public void second() {
        System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
        System.out.println();
    }
}

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

Результаты:

Из консоли видно, что первая и вторая синхронизированная задача не влияют друг на друга;

Кроме того, поскольку включена многопоточность, время выполнения первой задачи не ограничивается ее собственным временем выполнения, поэтому необходимо знать, что повторяющиеся операции могут вызвать отклонения данных.

Учебный материал 1: https://blog.csdn.net/MobiusStrip/article/details/85126261

Учебные материалы 2: https://www.jianshu.com/p/1defb0f22ed1

Исходный адрес: https://blog.csdn.net/pan_junbiao/article/details/109399280

рекомендация

отblog.csdn.net/pan_junbiao/article/details/109399280