springboot整合quartz实现动态添加、修改、删除、停止job(二)

作者:青山常在人不老   阅读 (3382)  |  收藏 (0)  |  点赞 (0)

摘要

本文将带您讲解如何一步步实现通过quartz实现添加、修改、删除、停止job,以及优化quartz工具类,支持自动停止逻辑


原文链接:springboot整合quartz实现动态添加、修改、删除、停止job(二)

QuartzJobComponent.java

package com.example.demo.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.TriggerUtils;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.example.demo.constant.GloabalConstant;
import com.example.demo.entity.QuartzJobModule;

import lombok.extern.slf4j.Slf4j;

/**
 * @project: 郭鹏飞的博客(wwww.pengfeiguo.com)
 * @description: qz工具类
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-09-04 825338623@qq.com Create 1.0
 * @copyright ©2019-2020 。
 */
@Slf4j
@Component
public class QuartzJobComponent {

    @Autowired
    private Scheduler scheduler;

    /**
     * @Description: 添加一个定时任务
     * @param quartzModel
     */
    public void addJob(QuartzJobModule quartzModel) {
        if (quartzModel.verify()) {
            try {
                JobDetail job = JobBuilder.newJob(quartzModel.getJobClass())
                    .withIdentity(quartzModel.getJobName(), quartzModel.getJobGroupName())
                    .setJobData(quartzModel.getJobDataMap()).build();
                // 表达式调度构建器
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzModel.getCron());
                // 按新的cronExpression表达式构建一个新的trigger
                Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(quartzModel.getTriggerName(), quartzModel.getTriggerGroupName())
                    .startAt(quartzModel.getStartTime()).endAt(quartzModel.getEndTime()).withSchedule(
                        scheduleBuilder)
                    .build();
                scheduler.scheduleJob(job, trigger);
                // 启动
                if (!scheduler.isShutdown()) {
                    scheduler.start();
                }
            }
            catch (SchedulerException e) {
                log.error("Add quartz job error, jobName = {}", quartzModel.getJobName());
            }

        }
        else {
            log.error("QuartzModel is invalid!");
        }
    }

    /**
     * @Description: 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)
     * @param jobName
     * @param cron
     */
    public void modifyJobTime(String jobName, String cron, Date startDate, Date endDate) {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME);

        try {
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            String oldTime = trigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(cron)) {
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
                // 按新的cronExpression表达式重新构建trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
                    .startAt(startDate)
                    .endAt(endDate).withSchedule(scheduleBuilder).build();
                // 按新的trigger重新设置job执行
                scheduler.rescheduleJob(triggerKey, trigger);
            }
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description:修改任务,(可以修改任务名,任务类,触发时间)
     *                                      原理:移除原来的任务,添加新的任务
     * @param oldJobName
     *            :原任务名
     * @param jobName
     * @param jobclass
     * @param cron
     */
    public void modifyJob(String oldJobName, String jobName, Class jobclass, String cron) {
        /*
         * removeJob(oldJobName);
         * addJob(jobName, jobclass, cron);
         * System.err.println("修改任务"+oldJobName);
         */
        TriggerKey triggerKey = TriggerKey.triggerKey(oldJobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME);
        JobKey jobKey = JobKey.jobKey(oldJobName, GloabalConstant.QZ_JOB_GROUP_NAME);
        try {
            Trigger trigger = (Trigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            scheduler.pauseTrigger(triggerKey);// 停止触发器
            scheduler.unscheduleJob(triggerKey);// 移除触发器
            scheduler.deleteJob(jobKey);// 删除任务
            System.err.println("移除任务:" + oldJobName);

            JobDetail job = JobBuilder.newJob(jobclass).withIdentity(jobName,
                GloabalConstant.QZ_JOB_GROUP_NAME)
                .build();
            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
            // 按新的cronExpression表达式构建一个新的trigger
            Trigger newTrigger = TriggerBuilder.newTrigger().withIdentity(jobName,
                GloabalConstant.QZ_TRIGGER_GROUP_NAME)
                .withSchedule(scheduleBuilder).build();

            // 交给scheduler去调度
            scheduler.scheduleJob(job, newTrigger);

            // 启动
            if (!scheduler.isShutdown()) {
                scheduler.start();
                System.err.println("添加新任务:" + jobName);
            }
            System.err.println("修改任务【" + oldJobName + "】为:" + jobName);
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * @Description: 修改一个任务的触发时间
     * @param triggerName
     * @param triggerGroupName
     * @param cron
     */
    public void modifyJobTime(String triggerName, String triggerGroupName, String cron) {
        TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
        try {
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            String oldTime = trigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(cron)) {
                // trigger已存在,则更新相应的定时设置
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
                // 按新的cronExpression表达式重新构建trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
                // 按新的trigger重新设置job执行
                scheduler.resumeTrigger(triggerKey);
            }
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description 移除一个任务(使用默认的任务组名,触发器名,触发器组名)
     * @param jobName
     */
    public void removeJob(String jobName) {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME);
        JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME);
        try {
            Trigger trigger = (Trigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            scheduler.pauseTrigger(triggerKey);// 停止触发器
            scheduler.unscheduleJob(triggerKey);// 移除触发器
            scheduler.deleteJob(jobKey);// 删除任务
            System.err.println("移除任务:" + jobName);
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description: 移除一个任务
     * @param jobName
     * @param jobGroupName
     * @param triggerName
     * @param triggerGroupName
     */
    public void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, triggerGroupName);
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            scheduler.pauseTrigger(triggerKey);// 停止触发器
            scheduler.unscheduleJob(triggerKey);// 移除触发器
            scheduler.deleteJob(jobKey);// 删除任务
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description:暂停一个任务(使用默认组名)
     * @param jobName
     */
    public void pauseJob(String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME);
        try {
            scheduler.pauseJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description:暂停一个任务
     * @param jobName
     * @param jobGroupName
     */
    public void pauseJob(String jobName, String jobGroupName) {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            scheduler.pauseJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description:恢复一个任务(使用默认组名)
     * @param jobName
     */
    public void resumeJob(String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME);
        try {
            scheduler.resumeJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description:恢复一个任务
     * @param jobName
     * @param jobGroupName
     */
    public void resumeJob(String jobName, String jobGroupName) {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            scheduler.resumeJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description:启动所有定时任务
     */
    public void startJobs() {
        try {
            scheduler.start();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description 关闭所有定时任务
     */
    public void shutdownJobs() {
        try {
            if (!scheduler.isShutdown()) {
                scheduler.shutdown();
            }
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description: 立即运行任务,这里的立即运行,只会运行一次,方便测试时用。
     * @param jobName
     */
    public void triggerJob(String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME);
        try {
            scheduler.triggerJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 立即运行任务,这里的立即运行,只会运行一次,方便测试时用。
     * @param jobName
     * @param jobGroupName
     */
    public void triggerJob(String jobName, String jobGroupName) {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            scheduler.triggerJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 获取任务状态
     * @param jobName
     *            触发器名
     */
    public String getTriggerState(String jobName) {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME);
        String name = null;
        try {
            Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);
            name = triggerState.name();
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
        return name;
    }

    /**
     * @Description:获取最近5次执行时间
     * @param cron
     */
    public List<String> getRecentTriggerTime(String cron) {
        List<String> list = new ArrayList<String>();
        try {
            CronTriggerImpl cronTriggerImpl = new CronTriggerImpl();
            cronTriggerImpl.setCronExpression(cron);
            List<Date> dates = TriggerUtils.computeFireTimes(cronTriggerImpl, null, 5);
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
            for (Date date : dates) {
                list.add(dateFormat.format(date));
            }
        }
        catch (ParseException e) {
            log.error("GetRecentTriggerTime error, cron = {}", cron, e);
        }
        return list;
    }

}

该包中的类为quartz中job调用时实际业务处理类

package com.example.demo.qzComp;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import lombok.extern.slf4j.Slf4j;

/**
 * @project: 郭鹏飞的博客(wwww.pengfeiguo.com)
 * @description: quartz job 业务处理类
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-09-04 825338623@qq.com Create 1.0
 * @copyright ©2019-2020
 */
@DisallowConcurrentExecution
@Slf4j
public class TaskJobDetail extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        log.info("begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>>");
        String batchId = context.getJobDetail().getKey().getName();
        log.info("执行的任务id为:[{}]", batchId);
    }

}

注意:

  1. 该类为实际业务处理类,定时任务调起时,所有的业务都在此处写(该类不能使用Spring的注解,需要通过上下文来引入需要的bean)

  2. 我们可以看到,在该类中有一行代码:String batchId = context.getJobDetail().getKey().getName();该行代码会获取job的名称,我们可以在添加job的时候,将要处理的数据的唯一标识(比如id)设置为jobName,然后在这个业务处理类中就知道到底要处理哪个数据了。

该包中模拟的是前端请求添加、暂停、修改、停止、删除job的接口,在这个类中,同样有封装job的代码

package com.example.demo.controller;

import java.util.Calendar;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.common.Result;
import com.example.demo.constant.GloabalConstant;
import com.example.demo.entity.QuartzJobModule;
import com.example.demo.qzComp.TaskJobDetail;
import com.example.demo.utils.CronUtil;
import com.example.demo.utils.QuartzJobComponent;

/**
 * @project: 郭鹏飞的博客(wwww.pengfeiguo.com)
 * @description: quartz controller 。
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-09-04 825338623@qq.com Create 1.0
 * @copyright ©2019-2020
 */
@RestController
@RequestMapping("/job")
public class JobController {
    private final static Logger LOGGER = LoggerFactory.getLogger(JobController.class);

    @Autowired
    private QuartzJobComponent quartzJobComponent;

    @PostMapping("/add")
    @ResponseBody
    public Result save() {
        LOGGER.info("新增任务");
        try {
            QuartzJobModule quartzJobModule = new QuartzJobModule();
            Calendar cal = Calendar.getInstance();
            cal.set(Calendar.YEAR, 2020);
            cal.set(Calendar.MONTH, 8);
            cal.set(Calendar.DATE, 9);
            cal.set(Calendar.HOUR_OF_DAY, 12);
            cal.set(Calendar.MINUTE, 30);
            cal.set(Calendar.SECOND, 00);
            Date startDate = cal.getTime();// 任务开始日期为2020年9月9日12点30分

            Calendar endCal = Calendar.getInstance();
            endCal.set(Calendar.YEAR, 2020);
            endCal.set(Calendar.MONTH, 8);
            endCal.set(Calendar.DATE, 12);
            endCal.set(Calendar.HOUR_OF_DAY, 12);
            endCal.set(Calendar.MINUTE, 30);
            endCal.set(Calendar.SECOND, 00);
            Date endDate = endCal.getTime();// 任务结束日期为2020年9月12日12点30分

            quartzJobModule.setStartTime(CronUtil.getStartDate(startDate));
            quartzJobModule.setEndTime(CronUtil.getEndDate(endDate));
            // 注意:在后面的任务中需要通过这个JobName来获取你要处理的数据,因此您可以讲这个设置为你要处理的数据的主键,比如id
            quartzJobModule.setJobName("testJobId");
            quartzJobModule.setTriggerName("tesTriggerNmae");
            quartzJobModule.setJobGroupName(GloabalConstant.QZ_JOB_GROUP_NAME);
            quartzJobModule.setTriggerGroupName(GloabalConstant.QZ_TRIGGER_GROUP_NAME);

            String weeks = "1,2,3,5";// 该处模拟每周1,2,3,5执行任务
            String cronExpression = CronUtil
                .convertCronExpression(startDate,
                    endDate, weeks.split(","));
            quartzJobModule.setCron(cronExpression);
            quartzJobModule.setJobClass(TaskJobDetail.class);
            quartzJobComponent.addJob(quartzJobModule);
        }
        catch (Exception e) {
            e.printStackTrace();
            return Result.ok();
        }
        return Result.ok();
    }

    @PostMapping("/edit")
    @ResponseBody
    public Result edit() {
        LOGGER.info("编辑任务");
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, 2020);
        cal.set(Calendar.MONTH, 8);
        cal.set(Calendar.DATE, 12);
        cal.set(Calendar.HOUR_OF_DAY, 12);
        cal.set(Calendar.MINUTE, 30);
        cal.set(Calendar.SECOND, 00);
        Date startDate = cal.getTime();// 任务开始日期为2020年9月12日12点30分

        Calendar endCal = Calendar.getInstance();
        endCal.set(Calendar.YEAR, 2020);
        endCal.set(Calendar.MONTH, 8);
        endCal.set(Calendar.DATE, 24);
        endCal.set(Calendar.HOUR_OF_DAY, 12);
        endCal.set(Calendar.MINUTE, 30);
        endCal.set(Calendar.SECOND, 00);
        Date endDate = endCal.getTime();// 任务结束日期为2020年9月24日12点30分
        // "testJobId"为add方法添加的job的name
        quartzJobComponent.modifyJobTime("testJobId", "/10 *  * ? * *", startDate, endDate);
        return Result.ok();
    }

    @PostMapping("/pause")
    @ResponseBody
    public Result pause(String jobName, String jobGroup) {
        LOGGER.info("停止任务");
        quartzJobComponent.pauseJob("testJobId");
        return Result.ok();
    }

    @PostMapping("/resume")
    @ResponseBody
    public Result resume(String jobName, String jobGroup) {
        LOGGER.info("恢复任务");
        quartzJobComponent.removeJob("testJobId");
        return Result.ok();

    }

    @PostMapping("/remove")
    @ResponseBody
    public Result remove(String jobName, String jobGroup) {
        LOGGER.info("移除任务");
        quartzJobComponent.removeJob("testJobId");
        return Result.ok();
    }
}

注意:

  1. 夲示例在添加job时,jobGroupName和triggerGroupName都是用的是上面常量类中的常量,您在实际使用中,必须保证添加时的jobGroupName和triggerGroupName和后面要进行暂停、停止等操作时的值完全一致,否则不会起作用

  2. 我们知道,cron表达式还是有一些鸡肋的,比如无法设置9:50-10:10分的定时任务,因此我们在设置代码时尽量规避这类任务(CronUtil.convertCronExpression的作用就是生成一个cron表达式),如果您认为和您的业务有差距,请换成您自己的逻辑即可
    至此,所有的配置和代码都完成了,我们需要调用以下进行验证是否能够成功动态添加job,并且成功调起job

验证是否成功

访问http://localhost:8080/quartz/job/add 接口,此时数据库中的表中将会新增进入数据

file

file

同时我们稍等几秒,控制台上就会打印出如下内容:

2020-09-10 11:31:46.272  INFO 15308 --- [nio-8080-exec-1] c.example.demo.controller.JobController  : 新增任务
2020-09-10 11:31:46.339  INFO 15308 --- [nio-8080-exec-1] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_NON_CLUSTERED started.
2020-09-10 11:32:26.086  INFO 15308 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2020-09-10 11:32:26.151  INFO 15308 --- [eduler_Worker-1] com.example.demo.qzComp.TaskJobDetail    : begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>>
2020-09-10 11:32:26.151  INFO 15308 --- [eduler_Worker-1] com.example.demo.qzComp.TaskJobDetail    : 执行的任务id为:[testJobId]
2020-09-10 11:32:30.010  INFO 15308 --- [eduler_Worker-2] com.example.demo.qzComp.TaskJobDetail    : begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>>
2020-09-10 11:32:30.011  INFO 15308 --- [eduler_Worker-2] com.example.demo.qzComp.TaskJobDetail    : 执行的任务id为:[testJobId]
2020-09-10 11:32:35.023  INFO 15308 --- [eduler_Worker-3] com.example.demo.qzComp.TaskJobDetail    : begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>>
2020-09-10 11:32:35.023  INFO 15308 --- [eduler_Worker-3] com.example.demo.qzComp.TaskJobDetail    : 执行的任务id为:[testJobId]

说明程序调用成功,并且在任务处理类中成功获取到了jobName,可以继续进行后面的业务处理
其他操作不在验证,大家可以自己调用进行测试,现在把代码放出来:
github :https://github.com/wangmingweikong/spring-boot/tree/master/spring-boot-quartz

分类   项目开发逻辑
字数   19467

博客标签    quartz动态添加job  

评论