当前位置:   article > 正文

Quartz详解和使用CommandLineRunner在项目启动时初始化定时任务_quartz的commandlinerunner

quartz的commandlinerunner

之前与定时任务相关的一个文章记录:
springboot使用@Scheduled作定时任务详细用法

Quartz介绍

Quartz是一个完全由java编写的功能丰富的开源作业调度库,可以集成到几乎任何Java应用程序中,小到独立应用程序,大到大型的电子商务系统。Quartz可以用来创建执行数十,数百乃至数万个作业的简单或复杂的计划;作业的任务被定义为标准的Java组件,它可以执行几乎任何你可能编程的任务。而且Quartz Scheduler包含许多企业级功能,例如支持JTA事务和集群。

了解一下Quartz中涉及到的几个类概念:

  • SchedulerFactory:调度器工厂。这是一个接口,用于调度器的创建和管理。示例中使用的是Quartz中的默认实现。

  • Scheduler:任务调度器。它表示一个Quartz的独立运行容器,里面注册了多个触发器(Trigger)和任务实例(JobDetail)。两者分别通过各自的组(group)和名称(name)作为容器中定位某一对象的唯一依据,因此组和名称必须唯一(触发器和任务实例的组和名称可以相同,因为对象类型不同)。

  • Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。

  • JobDetail:Job实例。Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。

  • Trigger

    :触发器,描述触发Job执行的时间触发规则。

    • SimpleTrigger:当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择。
    • CronTrigger:通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等。
      整个Quartz运行调度的流程如下:
  1. 通过触发器工厂(SchedulerFactory的实现类)创建一个调度器(Scheduler);
  2. 创建一个任务实例(JobDetail),为它指定实现了Job接口的实现类(示例中的HelloWord.class),并指定唯一标识(Identity)组(示例中的“group1”)和名称(示例中的“myJob”);
  3. 创建一个触发器(Trigger),为它指定时间触发规则(示例中的simpleSchedule()生成的SimpleTrigger),并指定唯一标识(Identity)组(示例中的“group1”)和名称(示例中的“myTrigger”)
  4. 最后通过调度器(Scheduler)将任务实例(JobDetail)和触发器(Trigger)绑定在一起,并通过start()方法开启任务调度。

自定义CommandLineRunner类:

关于CommandLineRunner:平常开发中有可能需要实现在项目启动后执行的功能,SpringBoot提供的一种简单的实现方案就是添加一个model并实现CommandLineRunner接口,实现功能的代码放在实现的run方法中
这个类的run方法String… args是应用启动的时候可以传进来的参数,有两种方式可以传参

一种是命令行的方式传参,所以这个接口叫CommandLineRunner
另一种方法是通过IntelliJ IDEA配置参数
命令行传参
首先将应用打成jar包,然后运行如下命令行,这里传入三个参数

java -jar MyProject.jar my name is
或者在idea中配置:
在这里插入图片描述

在这里插入图片描述

代码:


import com.itheima.pinda.entity.ScheduleJobEntity;
import com.itheima.pinda.mapper.ScheduleJobMapper;
import com.itheima.pinda.utils.ScheduleUtils;
import lombok.extern.slf4j.Slf4j;
import org.quartz.CronTrigger;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 在项目启动时初始化定时任务
 */
@Component
@Slf4j
public class DispatchCommandLineRunner implements CommandLineRunner {
    @Autowired
    private Scheduler scheduler;
    @Autowired
    private ScheduleJobMapper scheduleJobMapper;

    @Override
    public void run(String... args) throws Exception {
        log.info("开始进行定时任务初始化...");
        //查询定时任务表schedule_job中所有的数据
        List<ScheduleJobEntity> list = scheduleJobMapper.selectList(null);//查询数据库
        for (ScheduleJobEntity scheduleJobEntity : list) {
            //获得触发器对象,调用ScheduleUtils中的方法
            CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJobEntity.getId());
            if(cronTrigger == null){
                //触发器对象为空,说明对应的定时任务还没有创建
                ScheduleUtils.createScheduleJob(scheduler,scheduleJobEntity);
            }else{
                //触发器对象不为空,说明对应的定时任务已经存在了,此时只需要更新
                ScheduleUtils.updateScheduleJob(scheduler,scheduleJobEntity);
            }
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

创建、更新定时任务

上面自定义的CommandLineRunner类中调用了工具类的方法来真正运行任务,需要写一个定时任务工具类,方便通过jobid得到触发器key和jobkey、获得表达式触发器
在这里创建、更新定时任务、立即执行某个特定任务


/**
 * 定时任务工具类
 *
 * @author
 */
public class ScheduleUtils {
    private final static String JOB_NAME = "TASK_";
    /**
     * 任务调度参数key
     */
    public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY";

    /**
     * 获取触发器key
     */
    public static TriggerKey getTriggerKey(String jobId) {
        return TriggerKey.triggerKey(JOB_NAME + jobId);
    }

    /**
     * 获取jobKey
     */
    public static JobKey getJobKey(String jobId) {
        return JobKey.jobKey(JOB_NAME + jobId);
    }

    /**
     * 获取表达式触发器
     */
    public static CronTrigger getCronTrigger(Scheduler scheduler, String jobId) {
        try {
            return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));
        } catch (SchedulerException e) {
            throw new PdException("getCronTrigger ERROR", e);
        }
    }

    /**
     * 创建定时任务
     */
    public static void createScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) {
        try {
            //构建job信息
            JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getId())).build();

            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
                    .withMisfireHandlingInstructionDoNothing();

            //按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJob.getId())).withSchedule(scheduleBuilder).build();

            //放入参数,运行时的方法可以获取
            jobDetail.getJobDataMap().put(JOB_PARAM_KEY, scheduleJob);

            scheduler.scheduleJob(jobDetail, trigger);

            //暂停任务
            if (scheduleJob.getStatus() == ScheduleStatus.PAUSE.getValue()) {
                pauseJob(scheduler, scheduleJob.getId());
            }
        } catch (SchedulerException e) {
            throw new PdException("CREATE ERROR", e);
        }
    }

    /**
     * 更新定时任务
     */
    public static void updateScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) {
        try {
            TriggerKey triggerKey = getTriggerKey(scheduleJob.getId());

            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
                    .withMisfireHandlingInstructionDoNothing();

            CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getId());

            //按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

            //参数
            trigger.getJobDataMap().put(JOB_PARAM_KEY, scheduleJob);

            scheduler.rescheduleJob(triggerKey, trigger);

            //暂停任务
            if (scheduleJob.getStatus() == ScheduleStatus.PAUSE.getValue()) {
                pauseJob(scheduler, scheduleJob.getId());
            }

        } catch (SchedulerException e) {
            throw new PdException("UPDATE ERROR", e);
        }
    }

    /**
     * 立即执行任务
     */
    public static void run(Scheduler scheduler, ScheduleJobEntity scheduleJob) {
        try {
            //参数
            JobDataMap dataMap = new JobDataMap();
            dataMap.put(JOB_PARAM_KEY, scheduleJob);

            scheduler.triggerJob(getJobKey(scheduleJob.getId()), dataMap);
        } catch (SchedulerException e) {
            throw new PdException("RUN ERROR", e);
        }
    }

    /**
     * 暂停任务
     */
    public static void pauseJob(Scheduler scheduler, String jobId) {
        try {
            scheduler.pauseJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new PdException("PAUSE ERROR", e);
        }
    }

    /**
     * 恢复任务
     */
    public static void resumeJob(Scheduler scheduler, String jobId) {
        try {
            scheduler.resumeJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new PdException("RESUME ERROR", e);
        }
    }

    /**
     * 删除定时任务
     */
    public static void deleteScheduleJob(Scheduler scheduler, String jobId) {
        try {
            scheduler.deleteJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new PdException("DELETE ERROR", e);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146

service层

@Service
public class ScheduleJobServiceImpl extends ServiceImpl<ScheduleJobMapper, ScheduleJobEntity> implements IScheduleJobService {
    @Autowired
    private Scheduler scheduler;
   // 在service层中调用ScheduleUtils.run的方法来立即执行任务   
@Override
    @Transactional(rollbackFor = Exception.class)
    public void run(String[] ids) {
        for (String id : ids) {
            ScheduleUtils.run(scheduler, baseMapper.selectById(id));
        }
    }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在controller中调用service的方法

  @PutMapping("/run/{id}")
    @ApiOperation("立即执行")
    public Result run(@PathVariable String id) {
        scheduleJobService.run(new String[]{id});

        return Result.ok();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

自定义QuartzJobBean

【QuartzJobBean】:
Quartz Job 接口的简单实现,应用传入的 JobDataMap 和 SchedulerContext 作为 bean 属性值。 这是合适的,因为每次执行都会创建一个新的 Job 实例。 JobDataMap 条目将使用相同的键覆盖 SchedulerContext 条目。
例如,假设 JobDataMap 包含一个值为“5”的键“myParam”:然后,Job 实现可以公开一个 int 类型的 bean 属性“myParam”来接收这样的值,即方法“setMyParam(int)” . 这也适用于复杂类型,如业务对象等。
在上面的 ScheduleUtils中使用JobBuilder.newJob(ScheduleJob.class)构建了job信息,需要拓展QuartzJobBean自定义一个定时任务类
ScheduleJob 继承自QuartzJobBean,程序会进入executeInternal来执行定时任务


/**
 * 定时任务类,进行智能调度操作
 */
public class ScheduleJob extends QuartzJobBean {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        ScheduleJobEntity scheduleJob =
                (ScheduleJobEntity) context.getMergedJobDataMap().
                        get(ScheduleUtils.JOB_PARAM_KEY);
        System.out.println(new Date() + "定时任务开始执行...,定时任务id:" + scheduleJob.getId());

        //记录定时任务相关的日志信息
        //封装日志对象
        ScheduleJobLogEntity logEntity = new ScheduleJobLogEntity();
        logEntity.setId(IdUtils.get());
        logEntity.setJobId(scheduleJob.getId());
        logEntity.setBeanName(scheduleJob.getBeanName());
        logEntity.setParams(scheduleJob.getParams());
        logEntity.setCreateDate(new Date());

        long startTime = System.currentTimeMillis();

        try{
            //通过反射调用目标对象,在目标对象中封装智能调度核心逻辑
            logger.info("定时任务准备执行,任务id为:{}",scheduleJob.getId());

            //获得目标对象
            Object target = SpringContextUtils.getBean(scheduleJob.getBeanName());
            //获得目标方法对象
            Method method = target.getClass().getDeclaredMethod("run", String.class, String.class, String.class, String.class);

            //通过反射调用目标对象的方法
            method.invoke(target,scheduleJob.getBusinessId(),scheduleJob.getParams(),scheduleJob.getId(),logEntity.getId());

            logEntity.setStatus(1);//成功
        }catch (Exception ex){
            logEntity.setStatus(0);//失败
            logEntity.setError(ExceptionUtils.getErrorStackTrace(ex));
            logger.error("定时任务执行失败,任务id为:{}",scheduleJob.getId());
        }finally {
            int times = (int) (System.currentTimeMillis() - startTime);
            logEntity.setTimes(times);//耗时

            IScheduleJobLogService scheduleJobLogService = SpringContextUtils.getBean(IScheduleJobLogService.class);
            scheduleJobLogService.save(logEntity);
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

智能调度组件

编写智能调度组件DispatchTask,封装的是智能调度的核心逻辑,此类中需要提供run方法,在前面的ScheduleJob定时任务中通过反射调用此run方法来完成智能调度。

/**
 * 智能调度组件
 */
@Slf4j
@Component("dispatchTask")
public class DispatchTask{
    /**
     * 智能调度
     * @param businessId
     * @param params
     * @param jobId
     * @param logId
     */
    public void run(String businessId, String params, String jobId, String logId) {
        log.info("智能调度正在执行,参数为:{},{},{},{}", businessId, params, jobId, logId);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

定时任务实体类:

@Data
@EqualsAndHashCode(callSuper = false)
@TableName("schedule_job")
public class ScheduleJobEntity implements Serializable {

    /**
     * id
     */
    @TableId(value = "id", type = IdType.INPUT)
    private String id;
    /**
     * 创建者
     */
    @TableField(fill = FieldFill.INSERT)
    private Long creator;
    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private Date createDate;
    /**
     * spring bean名称
     */
    private String beanName;
    /**
     * 参数
     */
    private String params;
    /**
     * cron表达式
     */
    private String cronExpression;
    /**
     * 任务状态  0:暂停  1:正常
     */
    private Integer status;
    /**
     * 备注
     */
    private String remark;
    /**
     * 业务id 机构id
     */
    private String businessId;
    /**
     * 更新者
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updater;
    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateDate;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

mapper接口:

/**
 * 定时任务  Mapper 接口
 */
@Component
@Mapper
public interface ScheduleJobMapper extends BaseMapper<ScheduleJobEntity> {

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

定时任务在配置成功后启动时会查询数据库,在控制台输出类似如下的相应内容:(会查询数据库,执行用户定义的定时任务)
在这里插入图片描述

时间触发器的一些写法

具体时间设定可参考
“0/10 * * * * ?” 每10秒触发
“0 0 12 * * ?” 每天中午12点触发
“0 15 10 ? * *” 每天上午10:15触发
“0 15 10 * * ?” 每天上午10:15触发
"0 15 10 * * ? " 每天上午10:15触发
“0 15 10 * * ? 2005” 2005年的每天上午10:15触发
“0 * 14 * * ?” 在每天下午2点到下午2:59期间的每1分钟触发
“0 0/5 14 * * ?” 在每天下午2点到下午2:55期间的每5分钟触发
“0 0/5 14,18 * * ?” 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
“0 0-5 14 * * ?” 在每天下午2点到下午2:05期间的每1分钟触发
“0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44触发
“0 15 10 ? * MON-FRI” 周一至周五的上午10:15触发
“0 15 10 15 * ?” 每月15日上午10:15触发
“0 15 10 L * ?” 每月最后一日的上午10:15触发
“0 15 10 ? * 6L” 每月的最后一个星期五上午10:15触发
“0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最后一个星期五上午10:15触发
“0 15 10 ? * 6#3” 每月的第三个星期五上午10:15触发
每隔5秒执行一次:
/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/58749
推荐阅读
相关标签
  

闽ICP备14008679号