Элегантное решение для динамического изменения выражения Cron запланированных задач Spring!
Элегантное решение для динамического изменения выражения Cron запланированных задач Spring!

существовать SpringBoot В проекте,мы можем пройти@EnableSchedulingаннотация Включить поддержку задач планирования,и пройти@Scheduledаннотация Быстро создайте сериюзапланированные задачи。

@Scheduled поддерживает следующие три способа настройки времени выполнения:

  • cron(expression):в соответствии сCronвыражение для выполнения。
  • fixedDelay(period):Выполнять через фиксированные промежутки времени,Независимо от продолжительности задачи,Интервал между выполнением двух задач всегда один и тот же.
  • fixedRate(period):Исполнение с фиксированной частотой,После запуска задачи,существуют всегда фиксированное время исполнения,Если время выполнения слишком велико,Вызывает пропуск выполнения в определенный момент (поздно),Задача будет выполнена немедленно.

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

Изменчивое и неизменяемое

По умолчанию,@Scheduledаннотацияотмеченныйзапланированные После того, как метод задачи существует, он больше не изменится. Весна существовать初始化 bean назад,Перехватывайте все постпроцессором@Scheduledаннотацияметод,И проанализировать соответствующие параметры аннотации,Ставьте соответствующие запланированные Список задач ожидает последующего унифицированного выполнения и обработки. запланированные Прежде чем задача будет фактически запущена, у нас есть возможность изменить такие параметры, как цикл выполнения задачи.

Другими словами,Мы можем либо пройтиapplication.propertiesКонфигурация Согласование документов@Valueаннотация Конкретизируйте задачи таким образомCronвыражение,Вы также можете пройтиCronTriggerЗагрузка и регистрация из базы данных или любого другого промежуточного программного обеспечения хранилища.запланированные задача. Это Spring Переменные детали, предоставленные нам.

Но зачастую нам хочется большего. Могут существовать запланированные Если задача уже выполнена, перейдите в динамическое Изменение выражения Cron, даже отключение определенных запланированных А что насчет задачи? Жаль,По умолчанию,Это невозможно сделать,После регистрации и выполнения задачи,Параметры, используемые для регистрации, фиксированы.,Это неизменяемая часть.

создание и разрушение

Поскольку он неизменяем после создания,Затем разрушьте его, а затем восстановите. С тех пор,Наше мышление,существуют Сохранять критически важную информацию во время регистрации,И проверьте, не изменилась ли Конфигурация, с помощью другой запланированной задачи.,если есть изменения,Просто убей «бывшего»,Вместо. если нет изменений,Просто оставьте все как есть.

Сначала сделаем простую абстракцию задачи, чтобы облегчить унифицированную идентификацию и управление:

Язык кода:javascript
копировать
public interface IPollableService {
    /**
     * Метод исполнения
     */
    void poll();

    /**
     * Получить периодическое выражение
     *
     * @return CronExpression
     */
    default String getCronExpression() {
        return null;
    }

    /**
     * Получить название задачи
     *
     * @return Название задачи
     */
    default String getTaskName() {
        return this.getClass().getSimpleName();
    }
}

Самое главное этоgetCronExpression()метод,Каждая реализация службы синхронизации может управлять своими собственными выражениями.,Изменение и неизменность,Последнее слово остается за вами. Что касается того, где его взять,Как получить,Пожалуйста, действуйте по своему усмотрению. Следующий,Это для реализации динамической регистрации задач:

Язык кода:javascript
копировать
@Configuration
@EnableAsync
@EnableScheduling
public class SchedulingConfiguration implements SchedulingConfigurer, ApplicationContextAware {
    private static final Logger log = LoggerFactory.getLogger(SchedulingConfiguration.class);
    private static ApplicationContext appCtx;
    private final ConcurrentMap<String, ScheduledTask> scheduledTaskHolder = new ConcurrentHashMap<>(16);
    private final ConcurrentMap<String, String> cronExpressionHolder = new ConcurrentHashMap<>(16);
    private ScheduledTaskRegistrar taskRegistrar;

    public static synchronized void setAppCtx(ApplicationContext appCtx) {
        SchedulingConfiguration.appCtx = appCtx;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        setAppCtx(applicationContext);
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        this.taskRegistrar = taskRegistrar;
    }

    /**
     * обновитьзапланированные выражение задачи
     */
    public void refresh() {
        Map<String, IPollableService> beanMap = appCtx.getBeansOfType(IPollableService.class);
        if (beanMap.isEmpty() || taskRegistrar == null) {
            return;
        }
        beanMap.forEach((beanName, task) -> {
            String expression = task.getCronExpression();
            String taskName = task.getTaskName();
            if (null == expression) {
                log.warn("запланированные Выражение задачи задачи [{}] не Конфигурация или Ошибка конфигурации, проверьте «Конфигурация», taskName);
                return;
            }
            // Если время выполнения политики изменилось, отмените задачу текущей политики и перерегистрируйте задачу.
            boolean unmodified = scheduledTaskHolder.containsKey(beanName) && cronExpressionHolder.get(beanName).equals(expression);
            if (unmodified) {
                log.info("запланированные Выражение задачи Task[{}] не изменилось и его не нужно обновлять", taskName);
                return;
            }
            Optional.ofNullable(scheduledTaskHolder.remove(beanName)).ifPresent(existTask -> {
                existTask.cancel();
                cronExpressionHolder.remove(beanName);
            });
            if (ScheduledTaskRegistrar.CRON_DISABLED.equals(expression)) {
                log.warn("запланированные Выражение задачи задача[{}] отключено и не будет запланировано к выполнению", taskName);
                return;
            }
            CronTask cronTask = new CronTask(task::poll, expression);
            ScheduledTask scheduledTask = taskRegistrar.scheduleCronTask(cronTask);
            if (scheduledTask != null) {
                log.info("запланированные задача[{}] загружена, текущее выражение задачи — [{}]", taskName, expression);
                scheduledTaskHolder.put(beanName, scheduledTask);
                cronExpressionHolder.put(beanName, expression);
            }
        });
    }
}

Дело в том, чтобы сэкономитьScheduledTaskссылка на объект,Это ключ к контролю запуска и остановки задач. Выражение «-» служит специальным знаком.,Используется для отключения определенных запланированных задач.

Конечно, отключенные задачи можно «воскресить», переназначив новые выражения Cron. После выполнения вышеизложенного нам также понадобится запланированная задача для динамического мониторинга и обновления конфигурации запланированной задачи:

Язык кода:javascript
копировать
@Component
public class CronTaskLoader implements ApplicationRunner {
    private static final Logger log = LoggerFactory.getLogger(CronTaskLoader.class);
    private final SchedulingConfiguration schedulingConfiguration;
    private final AtomicBoolean appStarted = new AtomicBoolean(false);
    private final AtomicBoolean initializing = new AtomicBoolean(false);

    public CronTaskLoader(SchedulingConfiguration schedulingConfiguration) {
        this.schedulingConfiguration = schedulingConfiguration;
    }

    /**
     * запланированные задачи Конфигурацияобновить
     */
    @Scheduled(fixedDelay = 5000)
    public void cronTaskConfigRefresh() {
        if (appStarted.get() && initializing.compareAndSet(false, true)) {
            log.info("Начинается динамическая загрузка запланированных задач>>>>>>");
            try {
                schedulingConfiguration.refresh();
            } finally {
                initializing.set(false);
            }
            log.info("Динамическая загрузка запланированной задачи завершена<<<<<<");
        }
    }

    @Override
    public void run(ApplicationArguments args) {
        if (appStarted.compareAndSet(false, true)) {
            cronTaskConfigRefresh();
        }
    }
}

конечно,Вы также можете интегрировать эту часть кода непосредственно вSchedulingConfigurationсередина,Но для удобства расширения,Здесь мы по-прежнему разделяем выполнение и запуск. В конце концов, помимо запуска обновления с помощью запланированных задач,Вы также можете вручную запустить обновление с помощью кнопки на интерфейсе существования.,Или обновите через обратный вызов механизма сообщений. В этой части, пожалуйста, не стесняйтесь использовать ее в соответствии с реальной деловой ситуацией.

проверять

Создаем прототип проекта и три простых запланированных задачи Приходитьпроверять Вниз,Первая задача — это задача с фиксированным циклом выполнения.,Предположим, что его выражение Cron никогда не меняется.,Так:

Язык кода:javascript
копировать
@Service
public class CronTaskBar implements IPollableService {
    @Override
    public void poll() {
        System.out.println("Say Bar");
    }

    @Override
    public String getCronExpression() {
        return "0/1 * * * * ?";
    }
}

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

Язык кода:javascript
копировать
@Service
public class CronTaskFoo implements IPollableService {
    private static final Random random = new SecureRandom();

    @Override
    public void poll() {
        System.out.println("Say Foo");
    }

    @Override
    public String getCronExpression() {
        return "0/" + (random.nextInt(9) + 1) + " * * * * ?";
    }
}

Третья миссия великолепна,Это как выключатель света,существования включает и отключает повторяющиеся горизонтальные прыжки:

Язык кода:javascript
копировать
@Service
public class CronTaskUnavailable implements IPollableService {
    private String cronExpression = "-";
    private static final Map<String, String> map = new HashMap<>();

    static {
        map.put("-", "0/1 * * * * ?");
        map.put("0/1 * * * * ?", "-");
    }

    @Override
    public void poll() {
        System.out.println("Say Unavailable");
    }

    @Override
    public String getCronExpression() {
        return (cronExpression = map.get(cronExpression));
    }
}

Если вышеуказанные шаги выполнены правильно, вы должны увидеть в журнале вывод, подобный этому:

Язык кода:javascript
копировать
Начинается динамическая загрузка запланированных задач>>>>>>
запланированные Выражение задачи Task[CronTaskBar] не изменилось и его не нужно обновлять.
запланированные задача[CronTaskFoo] загружена, текущее выражение задачи — [0/6 * * * * ?]
запланированные Выражение задачи задача[CronTaskUnavailable] отключено и не будет запланировано для выполнения.
Динамическая загрузка запланированной задачи завершена<<<<<<
Say Bar
Say Bar
Say Foo
Say Bar
Say Bar
Say Bar
Начинается динамическая загрузка запланированных задач>>>>>>
запланированные Выражение задачи Task[CronTaskBar] не изменилось и его не нужно обновлять.
запланированные задача[CronTaskFoo] загружена, текущее выражение задачи — [0/3 * * * * ?]
запланированные задача[CronTaskUnavailable] загружена, текущее выражение задачи — [0/1 * * * * ?]
Динамическая загрузка запланированной задачи завершена<<<<<<
Say Unavailable
Say Bar
Say Unavailable
Say Bar
Say Foo
Say Unavailable
Say Bar
Say Unavailable
Say Bar
Say Unavailable
Say Bar

краткое содержание

Мы существуем выше достигнутого динамического за счет регулярного обновления и перестройки задач. изменениеCronвыражение的需求,Может удовлетворить большинство сценариев проекта,И никакого дополнительного промежуточного программного обеспечения, такого как кварцы, не вводится.,Можно сказать, что он очень легкий и элегантный. конечно,Если у вас, читателей, есть лучший способ,Пожалуйста, не стесняйтесь просветить меня.

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose