springboot Quartz动态修改cron表达式

适用于:

     动态修改定时任务,根据数据库的定时任务进行任务的激活和暂停,带参定时任务,指定时间和执行次数的定时任务等。

  1. 概述: 在开发中有的时候需要去手动禁止和启用定时任务,修改定时任务的cron表达式然后再让其动态生效,之前有过SSM的类似的业务的开发但是忘记写下来了。。。只好重新温习了一次,加上最近比较流行springBoot所以升级了一下用springBoot来完成.
  2. 关联技术 SpringBoot、Quartz、mysql、thymeleaf (好像就这么多)
  3. 涉及核心API 类
      Scheduler – 调度器,定时任务的指派和停止、启用、删除、列举都由他得出;
      Job – 通过scheduler执行任务,任务类需要实现的接口;
      JobDetail – 定义Job的实例;
      Trigger – 触发Job的执行,旗下分 CronTrigger 根据Cron表达式动态执行和SimpleTrigger 根据指定时间和间隔去执行;
      JobBuilder – 定义和创建JobDetail实例的接口;
      TriggerBuilder – 定义和创建Trigger实例的接口;

     

  4. 具体流程 ()

        1)首先去手动创建一个调度器工厂对象-SchedulerFactoryBean;其实应该不用手动创建的但是为了顾及到业务的复杂性所以还是创建一个好用。

  @Bean
    public SchedulerFactoryBean schedulerFactory(){
        SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
        /*用于Quartz集群,启动时更新已存在的Job*/
        factoryBean.setOverwriteExistingJobs(true);
        /*定时任务开始启动后延迟5秒开始*/
        factoryBean.setStartupDelay(5);
        return factoryBean;
    }

         2)继承job,实现方法execute。此处手写的原因是因为我们需要对定时任务的功能进行扩展,比如文中提到的参数注入和执行日志记录.注意jobExecutionContext.getMergedJobDataMap() 它可以获得 JobDataMap这个是Job实例化的一些信息,可以用于定时任务的带参。如下:


import com.study.www.enums.ConfigEnum;
import com.study.www.model.Config;
import com.study.www.model.Logger;
import com.study.www.model.mapper.LoggerRepository;
import com.study.www.utils.TaskUtils;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;


//当上一个任务未结束时下一个任务需进行等待
@DisallowConcurrentExecution
@Component
public class MyJob implements Job {

    //注意此处的 Dao 直接 @Autowired 注入是获取不到的,我们可以通过Spring容器去进行手动注入
    static LoggerRepository loggerRepository;
    //定时任务日志落地
    static List<Logger> loggers=new ArrayList<>();
    //每10条日志进行一下落地
    private final static Integer SIZE=9;

    //execute会根据cron的规则进行执行
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Config config = (Config) jobExecutionContext.getMergedJobDataMap().get(ConfigEnum.SCHEDULEJOB.getCode());
        Long beginTime=null;
        if (config.getIsLogger()){
            beginTime=System.currentTimeMillis();
        }
        TaskUtils.invokMethod(config);
        if (beginTime != null) {
            //手动注入 Dao
            if (MyJob.loggerRepository == null){
                MyJob.loggerRepository = SpringUtils.getBean("loggerRepository");
            }
            saveSysLog(beginTime,config);
        }
    }


    @Async
    void saveSysLog(Long beginTime, Config config) {
        Logger logger = new Logger();
        logger.setBeginTime(beginTime);
        logger.setEndTime(System.currentTimeMillis());
        logger.setClassPath(config.getClassPath());
        logger.setMethName(config.getMethodName());
        logger.setName(config.getName());
        logger.setGroupName(config.getGroup());
        logger.setTime(System.currentTimeMillis()-beginTime);
        logger.setParams(config.getReqParms());
        if (loggers.size() > SIZE) {
            synchronized (MyJob.class) {
                loggerRepository.save(loggers);
                loggers.clear();
            }
        }else{
            loggers.add(logger);
        }
    }

}

            3)获取到调度器-Scheduler和JobBuilder以及TriggerBuilder

 /*****************  此三者均可复用,类似于 Factory 一样。故提出做公用 ********/
    //调度器 
    private static Scheduler scheduler;
    //JobBuilder
    private static JobBuilder jobBuilder;
    //触发器Builder
    private static TriggerBuilder<Trigger> triggerBuilder ;
    

    //毫秒的间隔时间
    private static final Integer MILLISECONDS=1000;

    @PostConstruct
    public void init(){
        //因为Job 被自己给实现了故此处应去指定对应的Job类去创建一个调度器
        QuartzTableManager.jobBuilder= JobBuilder.newJob(MyJob.class);
        QuartzTableManager.scheduler = schedulerFactoryBean.getScheduler();
        triggerBuilder = TriggerBuilder.newTrigger();
    }

            4)实现动态新增、删除定时任务的方法.此处的 trigger 触发机制有两种 一种是Cron 一种是 选择时间和时间间隔的。因为有一种业务情况为客户去手动选择时间进行定时任务发送。

    /**
     * 增加任务
     *
     * @param :com.study.www.model.config
     * @Date: 2018/2/26 9:57
     * @return: void
     */
     void addJob(Config config) throws SchedulerException {
        //得到调度器
        JobKey jobKey = this.getJobKey(config);
        //获得触发器
        TriggerKey triggerKey = TriggerKey.triggerKey(config.getName(), config.getGroup());
        Trigger trigger = scheduler.getTrigger(triggerKey);
        //判断触发器是否存在(如果存在说明之前运行过但是在当前被禁用了,如果不存在说明一次都没运行过)
        if (trigger == null) {
            //新建一个工作任务 指定任务类型为串接进行的
            JobDetail jobDetail = jobBuilder.withIdentity(jobKey).build();
            //将工作添加到工作任务当中去
            JobDataMap jobDataMap = jobDetail.getJobDataMap();
            jobDataMap.put(ConfigEnum.SCHEDULEJOB.getCode(), config);
            trigger = getTrigger(config, triggerKey);
            //在调度器中将触发器和任务进行组合
            scheduler.scheduleJob(jobDetail, trigger);
        } else {
            //按照新的规则进行
            trigger = getTrigger(config, triggerKey);
            //重启
            scheduler.rescheduleJob(triggerKey, trigger);
        }
    }
    
    /**
    * 注意 simpleTrigger 和 cronTrigger 互斥只可以选择一个
    */
    private  Trigger getTrigger(Config config,TriggerKey triggerKey ){
        Trigger trigger =null;
        if(config.getCount() == null){
            //将cron表达式进行转换
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(config.getCron());
            trigger = triggerBuilder.withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
        }else{
            //指定的时间和时间段来进行定时任务
            MutableTrigger build = SimpleScheduleBuilder.simpleSchedule()
                    .withRepeatCount(config.getCount())
                    .withIntervalInMilliseconds(config.getIntervalSecond() * MILLISECONDS)
                    .build();
            build.setStartTime(config.getStartTime());
            build.setEndTime(config.getEndTime());
            build.setKey(triggerKey);
            trigger = (Trigger)build;
        }
        return trigger;
    }

    /**
     * 删除任务
     *
     * @param : com.study.www.model.config
     * @Date: 2018/2/24 18:23
     * @return: void
     */
     void deleteJob(Config config) throws SchedulerException {
        //找到key值
        JobKey jobKey = this.getJobKey(config);
        //从触发器找到此任务然后进行删除
        scheduler.deleteJob(jobKey);
    }

    /**
     * 根据name和group得到任务的key
     *
     * @param :com.study.www.model.config
     * @Date: 2018/2/24 18:27
     * @return: org.quartz.JobKey
     */
     JobKey getJobKey(Config config) {
        return getJobKey(config.getName(), config.getGroup());
    }

     JobKey getJobKey(String name, String group) {
        return JobKey.jobKey(name, group);
    }

         4)创建对定时任务的方法进行反射执行的方法。如下:


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.study.www.config.SpringUtils;
import com.study.www.model.Config;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class TaskUtils {

    private static LocalVariableTableParameterNameDiscoverer localVariableTableParameterNameDiscoverer;

    public static void invokMethod(Config config) {
        Object obj = null;
        Class clazz = null;
        //通过Spring上下文去找 也有可能找不到
        try {
            obj = SpringUtils.getBean(config.getClassPath().split("\\.")[config.getClassPath().split("\\.").length - 1]);
            if (obj == null) {
                clazz = Class.forName(config.getClassPath());
                obj = clazz.newInstance();
            } else {
                clazz = obj.getClass();
            }
        } catch (Exception e) {
            throw new RuntimeException("ERROR:TaskUtils is Bean Create please check the classpath is`t right or not");
        }
        //方法执行
        try {
            //入参
            JSONObject jsonObject = null;
            String reqParms = config.getReqParms();
            if (reqParms != null && reqParms.length() > 0) {
                jsonObject = JSON.parseObject(reqParms);
            }
            //获得方法名
            Method[] methods = clazz.getMethods();
            for (Method method1 : methods) {
                //不带参的
                if ((config.getMethodName().equals(method1.getName()) && jsonObject== null) ){
                    method1.invoke(obj);
                    return;
                }
                //带参的
                if ((config.getMethodName().equals(method1.getName()) && method1.getParameterCount()== jsonObject.size()) ){
                    Object[] params = new Object[method1.getParameterCount()];
                    if (localVariableTableParameterNameDiscoverer == null){
                        localVariableTableParameterNameDiscoverer= new LocalVariableTableParameterNameDiscoverer();
                    }
                    String[] parameterNames = localVariableTableParameterNameDiscoverer.getParameterNames(method1);
                    Parameter[] parameters = method1.getParameters();
                    for (int i = 0; i < parameterNames.length; i++) {
                        Class<?> type = parameters[i].getType();
                        Object object = jsonObject.getObject(parameterNames[i], type);
                        params[i]=object;
                    }
                    method1.invoke(obj,params);
                    return;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("ERROR:TaskUtils is Bean the method execute please check the methodName is`t right or not");
        }
    }
}

      提示:  页面用户修改Cron 表达式如果需要对Cron表达式进行校验可以使用如下方法。

  //Cron表达式解析
 CronScheduleBuilder cronScheduleBuilder = 
        CronScheduleBuilder.cronSchedule(config.getCron());

   提示: 如果定时任务的带参为不固定的比如是另外的一个请求等,可以修改 步骤 5中的规则。例如可以在Config 中加个用来进行参数类型判断的字段,然后参数为 一个http请求这样可以做到在定时任务中进行一个请求后再任务的执行。

若时间充裕推荐这封博客其对Quartz将的更加详细,配合示例看可以有着事半功倍的效果。

http://ifeve.com/quartz-tutorial-using-quartz/

代码例子:

码云:https://gitee.com/zhuyanpengWorld/springboots/blob/master/quartz.rar

 

Spring Boot与Quartz的结合,可以实现动态配置定时任务。下面是一种实现方式: 1. 首先,在pom.xml文件添加Quartz和Spring Boot的相关依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> ``` 2. 创建一个Quartz配置类,用于配置Quartz的相关属性。可以使用@Configuration注解将该类声明为配置类,并使用@Bean注解将SchedulerFactoryBean实例化: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.SchedulerFactoryBean; @Configuration public class QuartzConfig { // 其他配置属性 @Bean public SchedulerFactoryBean schedulerFactoryBean() { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); // 配置其他属性 return schedulerFactoryBean; } } ``` 3. 创建定时任务类,实现Job接口,并重写execute方法,用于定义具体的定时任务逻辑: ```java import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class MyJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 定时任务逻辑 } } ``` 4. 在需要动态配置的地方,使用@Autowired注解注入SchedulerFactoryBean,并使用schedulerFactoryBean.getScheduler()获取Scheduler实例。然后,通过Scheduler实例来动态配置定时任务: ```java import org.quartz.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MyScheduler { @Autowired private SchedulerFactoryBean schedulerFactoryBean; public void scheduleJob(String jobName, String cronExpression) throws SchedulerException { Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobDetail jobDetail = JobBuilder.newJob(MyJob.class) .withIdentity(jobName) .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity(jobName + "Trigger") .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)) .build(); scheduler.scheduleJob(jobDetail, trigger); } } ``` 通过调用MyScheduler的scheduleJob方法,可以动态配置定时任务的名称和cron表达式。注意,这里的cron表达式用于定义定时任务的触发规则。 以上就是使用Spring Boot和Quartz实现动态配置定时任务的基本步骤。可以根据具体需求进行扩展和调整。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值