SpringBoot动态定时功能实现方案详解

目录
  • 业务场景
  • 环境准备
  • 实现方案
  • 归纳总结

业务场景

基于上篇程序,做了一版动态定时程序,然后发现这个定时程序需要在下次执行的时候会加载新的时间,所以如果改了定时程序不能马上触发,所以想到一种方法,在保存定时程序的时候将cron表达式传过去,然后触发定时程序,下面看看怎么实现

环境准备

开发环境

  • JDK 1.8
  • SpringBoot2.2.1
  • Maven 3.2+

开发工具

  • IntelliJ IDEA
  • smartGit
  • Navicat15

实现方案

基于上一版进行改进:

  • 先根据选择的星期生成cron表达式,保存到数据库里,同时更改cron表达式手动触发定时程序加载最新的cron表达式
  • 根据保存的cron表达式规则执行定时程序
  • 通过CommandLineRunner设置启动加载
  • 加上线程池,提高线程复用率和程序性能

加上ThreadPoolTaskScheduler,支持同步和异步两种方式:

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@Slf4j
public class ScheduleConfig implements SchedulingConfigurer , AsyncConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskScheduler());
    }
    @Bean(destroyMethod="shutdown" , name = "taskScheduler")
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("itemTask-");
        scheduler.setAwaitTerminationSeconds(600);
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        return scheduler;
    }
    @Bean(name = "asyncExecutor")
    public ThreadPoolTaskExecutor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setQueueCapacity(1000);
        executor.setKeepAliveSeconds(600);
        executor.setMaxPoolSize(20);
        executor.setThreadNamePrefix("itemAsyncTask-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
    @Override
    public Executor getAsyncExecutor() {
        return asyncExecutor();
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, objects) -> {
            log.error("异步任务异常,message {} , method {} , params" , throwable , method , objects);
        };
    }
}

加上一个SchedulerTaskJob接口:

public interface SchedulerTaskJob{
    void executeTask();
}

AbstractScheduler 抽象类,提供基本的功能

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
@Slf4j
@Component
@Data
public abstract class AbstractScheduler implements SchedulerTaskJob{
    @Resource(name = "taskScheduler")
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;
    @Override
    public void executeTask() {
        String cron = getCronString();
        Runnable task = () -> {
            // 执行业务
            doBusiness();
        };
        Trigger trigger = new Trigger()   {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                CronTrigger trigger;
                try {
                    trigger = new CronTrigger(cron);
                    return trigger.nextExecutionTime(triggerContext);
                } catch (Exception e) {
                    log.error("cron表达式异常,已经启用默认配置");
                    // 配置cron表达式异常,执行默认的表达式
                    trigger = new CronTrigger(getDefaultCron());
                    return trigger.nextExecutionTime(triggerContext);
                }
            }
        };
        threadPoolTaskScheduler.schedule(task , trigger);
    }
    protected abstract String getCronString();
    protected abstract void doBusiness();
    protected abstract String getDefaultCron();
}

实现类,基于自己的业务实现,然后事项抽象类,通过模板模式进行编程

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
@Service
@Slf4j
@Data
public class ItemSyncScheduler extends AbstractScheduler {
    @Value("${configtask.default.itemsync}")
    private String defaultCron ;
    private String cronString ;
    @Override
    protected String getCronString() {
        if (StrUtil.isNotBlank(cronString))  return cronString;
        SyncConfigModel configModel = syncConfigService.getOne(Wrappers.<SyncConfigModel>lambdaQuery()
                .eq(SyncConfigModel::getBizType, 1)
                .last("limit 1"));
        if (configModel == null) return defaultCron;
        return configModel.getCronStr();
    }
    @Override
    protected void doBusiness() {
        log.info("执行业务...");
        log.info("执行时间:{}"  , LocalDateTime.now());
       // 执行业务
    }
    @Override
    protected String getDefaultCron() {
        return defaultCron;
    }
}

如果更改了cron表达式,程序不会马上触发,所以直接开放一个接口出来,调用的时候,设置最新的表达式,然后重新调用定时程序

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class ItemSchedulerController {
    private ItemSyncScheduler itemSyncScheduler;
    @Autowired
    public ItemSchedulerController(ItemSyncScheduler itemSyncScheduler) {
        this.itemSyncScheduler= itemSyncScheduler;
    }
    @GetMapping(value = "/updateItemCron")
    @ApiOperation(value = "更新cron表达式")
    public void updateItemCron(@RequestParam("cronString") String cronString) {
        log.info("更新cron表达式...");
        log.info("cronString:{}" , cronString);
        itemSyncScheduler.setCronString(cronString);
        itemSyncScheduler.executeTask();
    }
}

实现CommandLineRunner ,实现Springboot启动加载

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
@Order(1)
public class SchedulerTaskRunner implements CommandLineRunner {
    private ItemSyncScheduler itemSyncScheduler;
    @Autowired
    public SchedulerTaskRunner(ItemSyncScheduler itemSyncScheduler) {
        this.itemSyncScheduler= itemSyncScheduler;
    }
    @Override
    public void run(String... args) throws Exception {
        itemSyncScheduler.executeTask();
    }
}

归纳总结

基于上一版定时程序的问题,做了改进,加上了线程池和做到了动态触发,网上的资料很多都是直接写明使用SchedulingConfigurer来实现动态定时程序,不过很多都写明场景,本文通过实际,写明实现方法,本文是在保存定时程序的时候,设置最新的cron表达式,调一下接口重新加载,还可以使用canal等中间件监听数据表,如果改了就再设置cron表达式,然后触发程序

到此这篇关于SpringBoot动态定时功能实现方案详解的文章就介绍到这了,更多相关SpringBoot动态定时内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Springboot实现动态定时任务流程详解

    目录 一.静态 二.动态 1.基本代码 2.方案详解 2.1 初始化 2.2 单次执行 2.3 停止任务 2.4 启用任务 三.小结 一.静态 静态的定时任务可以直接使用注解@Scheduled,并在启动类上配置@EnableScheduling即可 @PostMapping("/list/test1") @Async @Scheduled(cron = "0 * * * * ?") public void test1() throws Exception { Ob

  • SpringBoot设置动态定时任务的方法详解

    之前写过文章记录怎么在SpringBoot项目中简单使用定时任务,不过由于要借助cron表达式且都提前定义好放在配置文件里,不能在项目运行中动态修改任务执行时间,实在不太灵活. 经过网上搜索学习后,特此记录如何在SpringBoot项目中实现动态定时任务. 因为只是一个demo,所以只引入了需要的依赖: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <ar

  • SpringBoot 实现动态添加定时任务功能

    目录 代码结构 1.配置类 2.定时任务类型枚举 3.实际执行任务实现类 4.定时任务包装器 5.任务注册器(核心) 6.使用 最后 最近的需求有一个自动发布的功能, 需要做到每次提交都要动态的添加一个定时任务 代码结构 1. 配置类 package com.orion.ops.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conf

  • Springboot自带定时任务实现动态配置Cron参数方式

    目录 Springboot自带定时任务实现动态配置Cron参数 SpringBoot定时任务的四种实现方式(主要) spring动态配置cron表达式,不需要停服 SchedulingConfigurer接口实现动态加载cron表达式 Springboot自带定时任务实现动态配置Cron参数 同学们,我今天分享一下SpringBoot动态配置Cron参数.场景是这样子的:后台管理界面对定时任务进行管理,可动态修改执行时间,然后保存入库,每次任务执行前从库里查询时间,以达到动态修改Cron参数的效

  • SpringBoot+Quartz实现动态定时任务

    本文实例为大家分享了springBoot+Quartz实现动态定时任务的具体代码,供大家参考,具体内容如下 目前常用的几种任务调度 Timer,简单无门槛,一般也没人用. spring @Scheduled注解,一般集成于项目中,小任务很方便. 开源工具 Quartz,分布式集群开源工具,以下两个分布式任务应该都是基于Quartz实现的,可以说是中小型公司必选,当然也视自身需求而定. 分布式任务 XXL-JOB,是一个轻量级分布式任务调度框架,支持通过 Web 页面对任务进行 CRUD 操作,支

  • SpringBoot实现动态定时任务的示例代码

    目录 前言 配置文件 定时任务核心类 提供修改cron表达式的controller 前言 之前在SpringBoot项目中简单使用定时任务,不过由于要借助cron表达式且都提前定义好放在配置文件里,不能在项目运行中动态修改任务执行时间,实在不太灵活.现在我们就来实现可以动态修改cron表达式的定时任务. 配置文件 application-task.yml,其余的配置 application.yml 等就按照springBoot正常配置即可 task: cron: 0/10 * * * * ? t

  • SpringBoot动态定时功能实现方案详解

    目录 业务场景 环境准备 实现方案 归纳总结 业务场景 基于上篇程序,做了一版动态定时程序,然后发现这个定时程序需要在下次执行的时候会加载新的时间,所以如果改了定时程序不能马上触发,所以想到一种方法,在保存定时程序的时候将cron表达式传过去,然后触发定时程序,下面看看怎么实现 环境准备 开发环境 JDK 1.8 SpringBoot2.2.1 Maven 3.2+ 开发工具 IntelliJ IDEA smartGit Navicat15 实现方案 基于上一版进行改进: 先根据选择的星期生成c

  • springboot框架的全局异常处理方案详解

    系统框架搭建的前期过程中,为了约束代码规范,我们会对一些通用功能做一些处理,比如声明一些系统公用错误类.封装通用返回结果.统一异常处理等,这样做的优势是团队开发过程中能够形成统一的代码规范,增强代码可读性,同时又便于后期代码维护.本文主要介绍下框架中异常的处理: 1.声明全局异常处理类,并添加@ControllerAdvice和@RestController注解 代码如下: @ControllerAdvice @RestController public class GlobalExceptio

  • SpringBoot之通过BeanPostProcessor动态注入ID生成器案例详解

    在分布式系统中,我们会需要 ID 生成器的组件,这个组件可以实现帮助我们生成顺序的或者带业务含义的 ID. 目前有很多经典的 ID 生成方式,比如数据库自增列(自增主键或序列).Snowflake 算法.美团 Leaf 算法等等,所以,会有一些公司级或者业务级的 ID 生成器组件的诞生.本文就是通过 BeanPostProcessor 实现动态注入 ID 生成器的实战. 在 Spring 中,实现注入的方式很多,比如 springboot 的 starter,在自定义的 Configuratio

  • Python实现定时任务的八种方案详解

    目录 利用whileTrue:+sleep()实现定时任务 使用Timeloop库运行定时任务 利用threading.Timer实现定时任务 利用内置模块sched实现定时任务 利用调度模块schedule实现定时任务 利用任务框架APScheduler实现定时任务 APScheduler中的重要概念 Job作业 Trigger触发器 Executor执行器 Jobstore作业存储 Event事件 调度器 Scheduler的工作流程 使用分布式消息系统Celery实现定时任务 使用数据流工

  • Spring处理@Async导致的循环依赖失败问题的方案详解

    目录 简介 问题复现 原因分析 解决方案 方案1:懒加载 方案2:不让@Async的类有循环依赖 方案3:allowRawInjectionDespiteWrapping设置为true 为什么@Transactional不会导致失败 简介 说明 本文介绍SpringBoot中的@Async导致循环依赖失败的原因及其解决方案. 概述 我们知道,Spring解决了循环依赖问题,但Spring的异步(@Async)会使得循环依赖失败.本文将用实例来介绍其原因和解决方案. 问题复现 启动类 启动类添加@

  • 基于 D3.js 绘制动态进度条的实例详解

    D3 是什么 D3 的全称是(Data-Driven Documents),顾名思义可以知道是一个被数据驱动的文档.听名字有点抽象,说简单一点,其实就是一个 JavaScript 的函数库,使用它主要是用来做数据可视化的.如果你不知道什么是 JavaScript ,请先学习一下 JavaScript,推荐阮一峰老师的教程. JavaScript 文件的后缀名通常为 .js,故 D3 也常使用 D3.js 称呼.D3 提供了各种简单易用的函数,大大简化了 JavaScript 操作数据的难度.由于

  • Redis的持久化方案详解

    Redis支持RDB与AOF两种持久化机制,持久化可以避免因进程异常退出或down机导致的数据丢失问题,在下次重启时能利用之前的持久化文件实现数据恢复. RDB持久化 RDB持久化即通过创建快照(压缩的二进制文件)的方式进行持久化,保存某个时间点的全量数据.RDB持久化是Redis默认的持久化方式.RDB持久化的触发包括手动触发与自动触发两种方式. 手动触发 save, 在命令行执行save命令,将以同步的方式创建rdb文件保存快照,会阻塞服务器的主进程,生产环境中不要用 bgsave, 在命令

  • springboot2.3 整合mybatis-plus 高级功能及用法详解

    -学习并使用mybatis-plus的一些高级功能的用法例如: AR模式. 乐观锁 .逻辑删除 .自动填充.数据保护等功能 为了方便演示,咱们还是新建一个全新的项目 引入mp依赖 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> &l

  • ASP.NET Core扩展库之日志功能的使用详解

    上一篇我们对Xfrogcn.AspNetCore.Extensions扩展库功能进行了简单的介绍,从这一篇文章开始,我将逐步介绍扩展库中的核心功能.     日志作为非业务的通用领域基础功能,有非常多的技术实现,这些第三方库避免了我们花费时间去重复实现,不过,很多日志库配置复杂,不易于使用,入手较难,而有些库可能与ASP.NET Core的结合并不好.     如果我们没有对所使用的日志库进行详细了解,日志库也可能产生严重的问题,在我的开发生涯中,曾经遇到过多次因为日志库而导致的生产事故.  

  • SpringBoot实战之处理异常案例详解

    前段时间写了一篇关于实现统一响应信息的博文,根据文中实战操作,能够解决正常响应的一致性,但想要实现优雅响应,还需要优雅的处理异常响应,所以有了这篇内容. 作为后台服务,能够正确的处理程序抛出的异常,并返回友好的异常信息是非常重要的,毕竟我们大部分代码都是为了 处理异常情况.而且,统一的异常响应,有助于客户端理解服务端响应,并作出正确处理,而且能够提升接口的服务质量. SpringBoot提供了异常的响应,可以通过/error请求查看效果: 这是从浏览器打开的场景,也就是请求头不包括content

随机推荐