Home  >  Article  >  Java  >  How to implement SpringBoot dynamic scheduled tasks

How to implement SpringBoot dynamic scheduled tasks

王林
王林forward
2023-05-21 14:05:301187browse

Thread pool configuration class for executing scheduled tasks

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
 
@Configuration
public class SchedulingConfig {  
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // 定时任务执行线程池核心线程数  
        taskScheduler.setPoolSize(6);  
        taskScheduler.setRemoveOnCancelPolicy(true);  
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");  
        return taskScheduler;  
    }  
}

Paper class of ScheduledFuture

ScheduledFuture is the execution result of the ScheduledExecutorService scheduled task thread pool.

import java.util.concurrent.ScheduledFuture;
 
public final class ScheduledTask {
  
    volatile ScheduledFuture<?> future;
    /**  
     * 取消定时任务  
     */  
    public void cancel() {  
        ScheduledFuture<?> future = this.future;  
        if (future != null) {  
            future.cancel(true);  
        }  
    }  
}

Runnable interface implementation class

is called by the scheduled task thread pool to execute methods in the specified bean.

import com.ying.demo.utils.springContextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.util.Objects;
 
public class SchedulingRunnable implements Runnable {
 
   private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);
 
   private String beanName;
 
   private String methodName;
 
   private String params;
 
   public SchedulingRunnable(String beanName, String methodName) {
      this(beanName, methodName, null);
   }
 
   public SchedulingRunnable(String beanName, String methodName, String params) {
      this.beanName = beanName;
      this.methodName = methodName;
      this.params = params;
   }
 
   @Override
   public void run() {
      logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);
      long startTime = System.currentTimeMillis();
 
      try {
         Object target = springContextUtils.getBean(beanName);
 
         Method method = null;
         if (!StringUtils.isEmpty(params)) {
            method = target.getClass().getDeclaredMethod(methodName, String.class);
         } else {
            method = target.getClass().getDeclaredMethod(methodName);
         }
 
         ReflectionUtils.makeAccessible(method);
         if (!StringUtils.isEmpty(params)) {
            method.invoke(target, params);
         } else {
            method.invoke(target);
         }
      } catch (Exception ex) {
         logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);
      }
 
      long times = System.currentTimeMillis() - startTime;
      logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
   }
 
   @Override
   public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      SchedulingRunnable that = (SchedulingRunnable) o;
      if (params == null) {
         return beanName.equals(that.beanName) &&
               methodName.equals(that.methodName) &&
               that.params == null;
      }
 
      return beanName.equals(that.beanName) &&
            methodName.equals(that.methodName) &&
            params.equals(that.params);
   }
 
   @Override
   public int hashCode() {
      if (params == null) {
         return Objects.hash(beanName, methodName);
      }
 
      return Objects.hash(beanName, methodName, params);
   }
}

Scheduled task registration class

Used to add and delete scheduled tasks

@Component
public class CronTaskRegistrar implements DisposableBean {
 
   private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);
 
   @Autowired
   private TaskScheduler taskScheduler;
 
   public TaskScheduler getScheduler() {
      return this.taskScheduler;
   }
 
   public void addCronTask(Runnable task, String cronExpression) {
      addCronTask(new CronTask(task, cronExpression));
   }
 
   public void addCronTask(CronTask cronTask) {
      if (cronTask != null) {
         Runnable task = cronTask.getRunnable();
         if (this.scheduledTasks.containsKey(task)) {
            removeCronTask(task);
         }
 
         this.scheduledTasks.put(task, scheduleCronTask(cronTask));
      }
   }
 
   public void removeCronTask(Runnable task) {
      ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
      if (scheduledTask != null)
         scheduledTask.cancel();
   }
 
   public ScheduledTask scheduleCronTask(CronTask cronTask) {
      ScheduledTask scheduledTask = new ScheduledTask();
      scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
 
      return scheduledTask;
   }
 
 
   @Override
   public void destroy() {
      for (ScheduledTask task : this.scheduledTasks.values()) {
         task.cancel();
      }
 
      this.scheduledTasks.clear();
   }
}

Scheduled task example class

@Slf4j
@Component("taskDemo")
public class Task1 {  
    public void taskByParams(String params) {
        log.info("taskByParams执行时间:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        log.info("taskByParams执行有参示例任务:{}",params);
    }  
  
    public void taskNoParams() {
        log.info("taskByParams执行时间:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        log.info("taskNoParams执行无参示例任务");
    }
 
    public void test(String params) {
        log.info("test执行时间:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        log.info("test执行有参示例任务:{}",params);
    }
}

Database table design

CREATE TABLE `schedule_setting` (
  `job_id` int NOT NULL AUTO_INCREMENT COMMENT &#39;任务ID&#39;,
  `bean_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT &#39;bean名称&#39;,
  `method_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT &#39;方法名称&#39;,
  `method_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT &#39;方法参数&#39;,
  `cron_expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT &#39;cron表达式&#39;,
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT &#39;备注&#39;,
  `job_status` int DEFAULT NULL COMMENT &#39;状态(1正常 0暂停)&#39;,
  `create_time` datetime DEFAULT NULL COMMENT &#39;创建时间&#39;,
  `update_time` datetime DEFAULT NULL COMMENT &#39;修改时间&#39;,
  PRIMARY KEY (`job_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Entity class

@Data
public class ScheduleSetting extends Model<ScheduleSetting> {
   /**
    * 任务ID
    */
   @Id
   private Integer jobId;
   /**
    * bean名称
    */
   private String beanName;
   /**
    * 方法名称
    */
   private String methodName;
   /**
    * 方法参数
    */
   private String methodParams;
   /**
    * cron表达式
    */
   private String cronExpression;
   /**
    * 状态(1正常 0暂停)
    */
   private Integer jobStatus;
   /**
    * 备注
    */
   private String remark;
   /**
    * 创建时间
    */
   private Date createTime;
   /**
    * 更新时间
    */
   private Date updateTime;
}

Scheduled task warm-up

After the spring boot project is started, load the scheduled task with normal status in the database

@Service  
public class SysJobRunner implements CommandLineRunner {  
  
    private static final Logger logger = LoggerFactory.getLogger(SysJobRunner.class);  
  
    @Autowired  
    private CronTaskRegistrar cronTaskRegistrar;  
  
    @Override  
    public void run(String... args) {  
        // 初始加载数据库里状态为正常的定时任务  
        ScheduleSetting existedSysJob = new ScheduleSetting();
        List<ScheduleSetting> jobList = existedSysJob.selectList(new QueryWrapper<ScheduleSetting>().eq("job_status", 1));
        if (CollectionUtils.isNotEmpty(jobList)) {  
            for (ScheduleSetting job : jobList) {  
                SchedulingRunnable task = new SchedulingRunnable(job.getBeanName(), job.getMethodName(), job.getMethodParams());  
                cronTaskRegistrar.addCronTask(task, job.getCronExpression());  
            }  
            logger.info("定时任务已加载完毕...");  
        }  
    }  
}

Tool class

Used to get beans from the spring container

@Component  
public class SpringContextUtils implements ApplicationContextAware {  
  
    private static ApplicationContext applicationContext;  
  
    @Override  
    public void setApplicationContext(ApplicationContext applicationContext)  
            throws BeansException {  
        SpringContextUtils.applicationContext = applicationContext;  
    }  
  
    public static Object getBean(String name) {  
        return applicationContext.getBean(name);  
    }  
  
    public static <T> T getBean(Class<T> requiredType) {  
        return applicationContext.getBean(requiredType);  
    }  
  
    public static <T> T getBean(String name, Class<T> requiredType) {  
        return applicationContext.getBean(name, requiredType);  
    }  
  
    public static boolean containsBean(String name) {  
        return applicationContext.containsBean(name);  
    }  
  
    public static boolean isSingleton(String name) {  
        return applicationContext.isSingleton(name);  
    }  
  
    public static Class<? extends Object> getType(String name) {  
        return applicationContext.getType(name);  
    }  
}

Scheduled tasks: add/delete/modify/start/pause

@RestController
public class TestController {
 
   @Autowired
   private CronTaskRegistrar cronTaskRegistrar;
 
   /**
    * 添加定时任务
    *
    * @param sysJob
    * @return
    */
   @PostMapping("add")
   public boolean add(@RequestBody ScheduleSetting sysJob) {
      sysJob.setCreateTime(new Date());
      sysJob.setUpdateTime(new Date());
 
      boolean insert = sysJob.insert();
      if (!insert) {
         return false;
      }else {
         if (sysJob.getJobStatus().equals(1)) {// 添加成功,并且状态是1,直接放入任务器
            SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());
            cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());
         }
      }
      return insert;
   }
 
   /**
    * 修改定时任务
    *
    * @param sysJob
    * @return
    */
   @PostMapping("update")
   public boolean update(@RequestBody ScheduleSetting sysJob) {
      sysJob.setCreateTime(new Date());
      sysJob.setUpdateTime(new Date());
 
      // 查询修改前任务
      ScheduleSetting existedSysJob = new ScheduleSetting();
      existedSysJob = existedSysJob.selectOne(new QueryWrapper<ScheduleSetting>().eq("job_id", sysJob.getJobId()));
      // 修改任务
      boolean update = sysJob.update(new UpdateWrapper<ScheduleSetting>().eq("job_id", sysJob.getJobId()));
      if (!update) {
         return false;
      } else {
         // 修改成功,则先删除任务器中的任务,并重新添加
         SchedulingRunnable task1 = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
         cronTaskRegistrar.removeCronTask(task1);
         if (sysJob.getJobStatus().equals(1)) {// 如果修改后的任务状态是1就加入任务器
            SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());
            cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());
         }
      }
      return update;
   }
 
   /**
    * 删除任务
    *
    * @param jobId
    * @return
    */
   @PostMapping("del/{jobId}")
   public boolean del(@PathVariable("jobId") Integer jobId) {
      // 先查询要删除的任务信息
      ScheduleSetting existedSysJob = new ScheduleSetting();
      existedSysJob = existedSysJob.selectOne(new QueryWrapper<ScheduleSetting>().eq("job_id", jobId));
 
      // 删除
      boolean del = existedSysJob.delete(new QueryWrapper<ScheduleSetting>().eq("job_id", jobId));
      if (!del)
         return false;
      else {// 删除成功时要清除定时任务器中的对应任务
         SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
         cronTaskRegistrar.removeCronTask(task);
      }
      return del;
   }
 
   // 停止/启动任务
   @PostMapping("changesStatus/{jobId}/{stop}")
   public boolean changesStatus(@PathVariable("jobId") Integer jobId, @PathVariable("stop") Integer stop) {
      // 修改任务状态
      ScheduleSetting scheduleSetting = new ScheduleSetting();
      scheduleSetting.setJobStatus(stop);
      boolean job_id = scheduleSetting.update(new UpdateWrapper<ScheduleSetting>().eq("job_id", jobId));
      if (!job_id) {
         return false;
      }
      // 查询修改后的任务信息
      ScheduleSetting existedSysJob = new ScheduleSetting();
      existedSysJob = existedSysJob.selectOne(new QueryWrapper<ScheduleSetting>().eq("job_id", jobId));
 
      // 如果状态是1则添加任务
      if (existedSysJob.getJobStatus().equals(1)) {
         SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
         cronTaskRegistrar.addCronTask(task, existedSysJob.getCronExpression());
      } else {
         // 否则清除任务
         SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
         cronTaskRegistrar.removeCronTask(task);
      }
      return true;
   }

How to implement SpringBoot dynamic scheduled tasks

cron

cron expression syntax:

[second] [minute] [hour] [day] [month] [week] [year]

Note: [ Year] is not a required field, you can omit [Year], then there are 6 fields in total

How to implement SpringBoot dynamic scheduled tasks

## Wildcard description:

  • * represents all values. For example: setting * on the minute field means it will trigger every minute.

  • ? means no value is specified. The usage scenario is that you do not need to care about the current value of this field. For example: you want to trigger an operation on the 10th of every month, but you don’t care what day of the week it is, so you need to set the field of the week position to "?" The specific setting is 0 0 0 10 * ?

  • - represents the interval. For example, setting "10-12" on the hour means that it will be triggered at 10, 11, and 12 o'clock.

  • , means specifying multiple values, for example, setting "MON,WED,FRI" on the week field means triggering on Monday, Wednesday and Friday

  • / is used for incremental triggering. Set the number of seconds to "5/15", which means that starting from the 5th second, it will trigger every 15 seconds (ie, the 5th second, the 20th second, the 35th second and the 50th second). Set "1/3" in the day field to start on the 1st of each month and trigger every three days.

  • L represents the last meaning. In the day field setting, it represents the last day of the month (according to the current month, if it is February, it will also depend on whether it is a leap year), and in the week field it represents Saturday, which is equivalent to "7" or "SAT". If you add a number before "L", it means the last one of the data. For example, setting a format like "6L" on the week field means "the last Friday of this month"

  • W means the closest working day to the specified date (Monday to Friday) . For example, setting "15W" in the day field means that the trigger is triggered on the working day closest to the 15th of each month. If the 15th happens to be a Saturday, the trigger will be found on the nearest Friday (the 14th). If the 15th is a weekend, the trigger will be found on the nearest Monday (the 16th). If the 15th happens to be on a working day (Monday to Sunday) 5), it will be triggered on that day. When the specified format is "1W", it means triggering on the nearest working day after the 1st of each month. If the 1st falls on a Saturday, it will be triggered on Monday the 3rd. (Note, only specific numbers can be set before "W", and the interval "-" is not allowed).

  • # Serial number (indicating the day of the week of each month), for example, setting "6#3" on the week field indicates the third Saturday of each month. Note that if Specify "#5", if there is no Saturday in the fifth week, this configuration will not be triggered (it is perfect for Mother's Day and Father's Day); Tips: "L" and "W" can be used in combination . If "LW" is set on the day field, it means that it is triggered on the last working day of this month; the setting of the week field is not case-sensitive if English letters are used, that is, MON is the same as mon.

Example:

Execute every 5 seconds: */5 * * * * ?

Executed every 1 minute: 0 */1 * * * ?

Executed once every day at 23:00: 0 0 23 * * ?

Executed once every day at 1 am: 0 0 1 * * ?

Executed once every month at 1 am on the 1st day of the month: 0 0 1 1 * ?

Executed once at 23:00 on the last day of every month: 0 0 23 L * ?

Execute once every Saturday at 1 a.m.: 0 0 1 ? * L

Execute once at 26 minutes, 29 minutes, and 33 minutes: 0 26, 29, 33 * * * ?

Execute once every day at 0:00, 13:00, 18:00, and 21:00: 0 0 0,13,18,21 * * ?

cron online expression generator: http://tools.jb51.net/code/Quartz_Cron_create

The above is the detailed content of How to implement SpringBoot dynamic scheduled tasks. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete