Предисловие
После версии 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&
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