Maison  >  Article  >  Java  >  Comment implémenter les tâches planifiées dynamiques SpringBoot

Comment implémenter les tâches planifiées dynamiques SpringBoot

王林
王林avant
2023-05-21 14:05:301187parcourir

Classe de configuration du pool de threads pour l'exécution de tâches planifiées

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;  
    }  
}

La classe Paper de ScheduledFuture

ScheduledFuture est le résultat de l'exécution du pool de threads de tâches planifiées ScheduledExecutorService.

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);  
        }  
    }  
}

La classe d'implémentation d'interface exécutable

est appelée par le pool de threads de tâches planifiées pour exécuter des méthodes dans le bean spécifié.

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);
   }
}

Classe d'enregistrement de tâches chronométrées

Utilisé pour ajouter et supprimer des tâches chronométrées

@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();
   }
}

Classe d'exemple de tâches chronométrées

@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);
    }
}

Conception de table de base de données

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;

Classe d'entité

@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;
}

Échauffement des tâches chronométrées

Une fois le projet Spring Boot terminé démarré, chargez les tâches planifiées dans la base de données avec un statut normal

@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("定时任务已加载完毕...");  
        }  
    }  
}

Classe d'outils

Utilisé pour récupérer les beans du conteneur Spring

@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);  
    }  
}

Tâches planifiées : ajouter/supprimer/modifier/démarrer/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;
   }

Comment implémenter les tâches planifiées dynamiques SpringBoot

cron

cron Syntaxe de l'expression :

[seconde] [minute] [heure] [jour] [mois] [semaine] [année]

Remarque : [année] n'est pas un champ obligatoire, [année] peut être omis, il y en a donc 6 au total. Champ

Comment implémenter les tâches planifiées dynamiques SpringBoot

Description du caractère générique :

  • * représente toutes les valeurs. Par exemple : définir * sur le champ des minutes signifie qu'il se déclenchera toutes les minutes.

  •  ? signifie qu'aucune valeur n'est spécifiée. Le scénario d'utilisation est que vous n'avez pas besoin de vous soucier de la valeur actuelle de ce champ. Par exemple : vous souhaitez déclencher une opération le 10 de chaque mois, mais vous ne vous souciez pas du jour de la semaine, vous devez donc définir le champ de la position de la semaine sur " ? ". 0 0 0 10 * ?

  • - indiquant l'intervalle. Par exemple, régler « 10-12 » sur l'heure signifie qu'elle se déclenchera à 10, 11 et 12 heures.

  • , signifie spécifier plusieurs valeurs, par exemple, définir "MON,WED,FRI" sur le champ de la semaine signifie un déclenchement le lundi, mercredi et vendredi

  • / est utilisé pour le déclenchement incrémentiel. Réglez le nombre de secondes sur "5/15", ce qui signifie qu'à partir de la 5ème seconde, il se déclenchera toutes les 15 secondes (c'est-à-dire la 5ème seconde, la 20ème seconde, la 35ème seconde et la 50ème seconde). Définissez « 1/3 » dans le champ du jour pour commencer le 1er de chaque mois et déclencher tous les trois jours.

  • L signifie le dernier mot. Dans le champ jour, il représente le dernier jour du mois (selon le mois en cours, si c'est février, cela dépendra aussi s'il s'agit d'une année bissextile), et dans le champ semaine, il représente le samedi, qui est équivalent à "7" ou "SAT". Si vous ajoutez un chiffre avant "L", cela signifie la dernière des données. Par exemple, définir un format tel que "6L" sur le champ de la semaine signifie "le dernier vendredi de ce mois"

  • W signifie le jour ouvrable le plus proche (du lundi au vendredi) de la date spécifiée. Par exemple, définir "" sur. le champ jour 15W", indiquant qu'il se déclenche le jour ouvrable le plus proche du 15 de chaque mois. Si le 15 est un samedi, le déclencheur sera trouvé le vendredi le plus proche (le 14). Si le 15 est un week-end, le déclencheur sera trouvé le lundi le plus proche (le 16). un jour ouvrable (du lundi au dimanche) 5), il sera déclenché ce jour là. Lorsque le format précisé est « 1W », cela signifie le déclenchement le jour ouvré le plus proche après le 1er de chaque mois. Si le 1er tombe un samedi, il se déclenchera le lundi 3. (Remarque : seuls des nombres spécifiques peuvent être définis avant « W » et l'intervalle « - » n'est pas autorisé).

  • # Numéro de série (indiquant le jour de la semaine de chaque mois), par exemple, définir "6#3" sur le champ de la semaine signifie que c'est le troisième samedi de chaque mois. Notez que si vous spécifiez "#5". ", ce sera le jour exact du mois. S'il n'y a pas de samedi dans cinq semaines, cette configuration ne se déclenchera pas (parfait pour la fête des mères et la fête des pères) ; Astuces : "L" et "W" peuvent être utilisés en combinaison. Si "LW" est défini sur le champ jour, cela signifie qu'il est déclenché le dernier jour ouvrable de ce mois, le réglage du champ semaine n'est pas sensible à la casse si des lettres anglaises sont utilisées, c'est-à-dire que MON est le même ; comme lundi.

Exemple :

Exécuté toutes les 5 secondes : */5 * * * * ?

Exécuté toutes les 1 minute : 0 */1 * * * ?

Exécuté une fois par jour à 23h00 : 0 0 23 * * ?

Exécuté une fois par jour à 1h du matin : 0 0 1 * * ?

Exécuté une fois par mois à 1h du matin le 1er : 0 0 1 1 * ?

Exécuté une fois à 23h00 le le dernier jour de chaque mois :0 0 23 L * ?

Exécuter une fois tous les samedis à 1h du matin : 0 0 1 * L

Exécuter une fois à 26 minutes, 29 minutes et 33 minutes : 0 26,29,33 * * * ?

Exécuter une fois par jour à 0h00, 13h00, 18h00 et 21h00 : 0 0 0,13,18,21 * * ?

générateur d'expressions en ligne cron : http://tools.jb51.net/code/Quartz_Cron_create

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer