springboot系列之动态定时程序改进版(代码片段)

smileNicky smileNicky     2022-12-15     107

关键词:

业务场景

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

环境准备

  • 开发环境

    • JDK 1.8
    • SpringBoot2.2.1
    • Maven 3.2+
  • 开发工具

    • IntelliJ IDEA
    • smartGit
    • Navicat15

实现方案

基于上一版进行改进:

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

加上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系列之动态定时程序改进版(代码片段)

...定时程序,下面看看怎么实现环境准备开发环境JDK1.8SpringBoot2.2.1Maven3.2+开发工具IntelliJIDEAsmartGitNavicat15实现方案基于上一版进行改进:先根据选择的星期生成cron表达式,保存到数据库里,同时更改cron表达式手... 查看详情

springboot系列之动态定时程序(代码片段)

...达式,然后定时执行定时程序环境准备开发环境JDK1.8SpringBoot2.2.1Maven3.2+开发工具IntelliJIDEAsmartGitNavicat15在IDEA里集成阿里的https://start.aliyun.com,创 查看详情

springboot系列之动态定时程序

...ron表达式,然后定时执行定时程序环境准备开发环境JDK1.8SpringBoot2.2.1Maven3.2+开发工具IntelliJIDEAsmartGitNavicat15在IDEA里集成阿里的​​https://start.aliyun.com​​​,创建一个​​SpringInitializr​​项目:选择jdk版本, 查看详情

springboot系列之动态生成cron表达式执行定时程序(代码片段)

...达式,然后定时执行定时程序环境准备开发环境JDK1.8SpringBoot2.2.1Maven3.2+开发工具IntelliJIDEAsmartGitNavicat15在IDEA里集成阿里的https://start.aliyun.com,创 查看详情

springboot系列之动态定时程序(代码片段)

...达式,然后定时执行定时程序环境准备开发环境JDK1.8SpringBoot2.2.1Maven3.2+开发工具IntelliJIDEAsmartGitNavicat15在IDEA里集成阿里的https://start.aliyun.com,创建一个SpringInitializr项目:选择jdk版本,和maven打包方式,选择... 查看详情

重学springboot系列之异步任务与定时任务(代码片段)

重学SpringBoot系列之异步任务与定时任务实现Async异步任务环境准备同步调用异步调用异步回调为异步任务规划线程池SpringBoot任务线程池自定义线程池优雅地关闭线程池通过@Scheduled实现定时任务开启定时任务方法不同定时方式... 查看详情

java之springboot入门到精通idea版springboot原理分析,springboot监控(一篇文章精通系列)下(代码片段)

目录Java之SpringBoot入门到精通【IDEA版】(一篇文章精通系列)【上】Java之SpringBoot入门到精通【IDEA版】SpringBoot整合其他框架【Junit,Redis,MyBatis】(一篇文章精通系列)【中】Java之SpringBoot入门到精通【IDEA版】SpringBo... 查看详情

玩转springboot之定时任务详解(代码片段)

...口三、多线程定时任务 阅读正文:回到顶部序言使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:一、基于注解(@Scheduled)二、基于接口(SchedulingConfigurer)前者相信大家都很熟悉,但是实际使用中我们往往... 查看详情

java之springboot入门到精通idea版(一篇文章精通系列)上(代码片段)

一、SpringBoot概述SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的... 查看详情

springboot2.x系列教程48--多数据源配置之aop动态切换数据源

SpringBoot2.x系列教程48--多数据源配置之AOP动态切换数据源作者:一一哥在上一节中,我通过分包的方式实现了多数据源的配置,接下来我通过AOP切面的方式,带领大家实现第二种多数据源配置方式,该方式是在前面案例的基础上... 查看详情

springboot2.x系列教程48--多数据源配置之aop动态切换数据源

SpringBoot2.x系列教程48--多数据源配置之AOP动态切换数据源作者:一一哥在上一节中,我通过分包的方式实现了多数据源的配置,接下来我通过AOP切面的方式,带领大家实现第二种多数据源配置方式,该方式是在前面案例的基础上... 查看详情

java之springboot入门到精通idea版springboot整合其他框架junit,redis,mybatis(一篇文章精通系列)中(代码片段)

SpringBoot整合其他框架【Junit,Redis,MyBatis】一、SpringBoot整合Junit①搭建SpringBoot工程②引入starter-test起步依赖③编写测试类(1)在启动类傍边其他类④添加测试相关注解⑤编写测试方法二、SpringBoot整合Redis1、搭建SpringBoot工... 查看详情

玩转springboot之定时任务详解(代码片段)

序言使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:一、基于注解(@Scheduled)二、基于接口(SchedulingConfigurer)前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务... 查看详情

springboot系列之集成scala开发api接口(代码片段)

SpringBoot系列之集成Scala开发API接口最近需要用scala去写一些数据同步的程序,结合ETL实现,因为不熟悉scala语法,所以想到scala里结合springboot框架,快速开发,并没有系统学习scala,有些代码可能不够精简,有问题欢迎提出后端主... 查看详情

补习系列-springboot单元测试之道(代码片段)

目录目标一、About单元测试二、AboutJunit三、SpringBoot-单元测试项目依赖测试样例四、Mock测试五、最后目标了解单元测试的背景了解如何利用springboot实现接口的测试了解如何利用mokito做代码的mock一、About单元测试单元测试其实是... 查看详情

springboot动态定时任务(代码片段)

SpringBoot动态定时任务前言配置文件定时任务核心类提供修改cron表达式的controller前言之前在SpringBoot项目中简单使用定时任务,不过由于要借助cron表达式且都提前定义好放在配置文件里,不能在项目运行中动态修改任务执... 查看详情

springboot之scheduled定时器

...务,对于项目数据的及时性处理,很重要;这里我们讲解SpringBoot定时任务@Scheduled,这是Spring提供的一个注解,使用比较简单。2、开启定时任务我们需要首先在启动类上添加@EnableScheduling注解,启动自动任务,保证定时任 查看详情

springboot整合quartz定时任务管理springboot系列18(代码片段)

SpringCloud大型系列课程正在制作中,欢迎大家关注与提意见。程序员每天的CV与板砖,也要知其所以然,本系列课程可以帮助初学者学习SpringBooot项目开发与SpringCloud微服务系列项目开发Quartz是由Java语言编写,是Open... 查看详情