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

 更新时间:2022年11月15日 15:20:37   作者:smileNicky  
在SpringBoot项目中简单使用定时任务,不过由于要借助cron表达式且都提前定义好放在配置文件里,不能在项目运行中动态修改任务执行时间,实在不太灵活。现在我们就来实现可以动态修改cron表达式的定时任务,感兴趣的可以了解一下

业务场景

基于上篇程序,做了一版动态定时程序,然后发现这个定时程序需要在下次执行的时候会加载新的时间,所以如果改了定时程序不能马上触发,所以想到一种方法,在保存定时程序的时候将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动态定时内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring中IOC和AOP的深入讲解

    Spring中IOC和AOP的深入讲解

    这篇文章主要给大家介绍了关于Spring中IOC和AOP的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-12-12
  • Java日常练习题,每天进步一点点(19)

    Java日常练习题,每天进步一点点(19)

    下面小编就为大家带来一篇Java基础的几道练习题(分享)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧,希望可以帮到你
    2021-07-07
  • java IP归属地功能实现详解

    java IP归属地功能实现详解

    前一阵子抖音和微博开始陆续上了IP归属地的功能,引起了众多热议,有大批在国外的老铁们开始"原形毕露",被定位到国内来,那么IP归属到底是怎么实现的呢?那么网红们的归属地到底对不对呢
    2022-07-07
  • java操作PDF文件方法之转换、合成、切分

    java操作PDF文件方法之转换、合成、切分

    最近需要做⼀个把多个pdf报告合并成⼀个以⽅便预览的需求,下面这篇文章主要给大家介绍了关于java操作PDF文件方法之转换、合成、切分的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-01-01
  • 在Java的Struts框架下进行web编程的入门教程

    在Java的Struts框架下进行web编程的入门教程

    这篇文章主要介绍了在Java的Struts框架下进行web编程的入门教程,需要的朋友可以参考下
    2015-11-11
  • java运行错误A JNI error的解决方案

    java运行错误A JNI error的解决方案

    这篇文章主要介绍了java运行错误A JNI error的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • 详解mybatis.generator配上最新的mysql 8.0.11的一些坑

    详解mybatis.generator配上最新的mysql 8.0.11的一些坑

    这篇文章主要介绍了详解mybatis.generator配上最新的mysql 8.0.11的一些坑,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-10-10
  • 如何通过idea实现springboot集成mybatis

    如何通过idea实现springboot集成mybatis

    这篇文章主要介绍了如何通过idea实现springboot集成mybatis,使用springboot 集成 mybatis后,通过http请求接口,使得通过http请求可以直接操作数据库,本文结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-09-09
  • Java高并发测试框架JCStress详解

    Java高并发测试框架JCStress详解

    这篇文章主要介绍了Java高并发测试框架JCStress,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • Java基础之多线程

    Java基础之多线程

    以下是我们Java基础多线程的一些知识点总结,看完以后会觉得多线程也可以这么简单,小编精心推荐,希望能对大家有所帮助
    2018-09-09

最新评论