SpringBoot整合quartz动态配置定时任务

前言

  • 在实际的开发工作中,大多定时任务不是写死的,一般都会配置动态参数并实例化存储到数据库中,从而实现定时任务的动态配置,下面通过一个简单的开发子模块实例来进行演示(源码地址在最后)

代码实现

1.新建SpringBoot项目,引入quartz相关依赖

<dependency>
	<groupId>org.quartz-scheduler</groupId>
	<artifactId>quartz</artifactId>
	<version>2.2.1</version>
	<exclusions>
		<exclusion>
			<artifactId>slf4j-api</artifactId>
			<groupId>org.slf4j</groupId>
		</exclusion>
	</exclusions>
</dependency>

2.订单Entity类。业务预实现用户添加订单后根据订单属性项来为其推送热文邮件,这里解释下业务,用户新增订单后,会记录如下各项,其中发送频率字段(frequency)选项包括每天/每周/关闭,这个字段对每一个quartz定时器的cron表达式起了限定作用

/**
 * 推送订单
 * @author wuyifan
 */
@Data
public class PushOrder implements Serializable {
     

    /**
     * 订单id(数据库唯一主键)
     */
    private Integer pid;
    /**
     * 订单名称
     */
    private String workOrder;
    /**
     * 订单关键词
     */
    private String keywords;
    /**
     * 收件人地址(例:[email protected])
     */
    private String toAddress;
    /**
     * 发送频率
     */
    private String frequency;
    /**
     * 创建时间
     */
    @JSONField(format = "yyyy-MM-dd HH:mm:ss")
    private String createDate;
    /**
     * 更新时间
     */
    @JSONField(format = "yyyy-MM-dd HH:mm:ss")
    private String updateDate;
    /**
     * 任务名称(以下三个字段用来存储对应定时器信息)
     */
    private String jobName;
    /**
     * 任务分组
     */
    private String jobGroup;
    /**
     * cron表达式
     */
    private String cronExpression;
    /**
     * 用户id
     */
    private String uid;
}

先贴出部分quartz工具类

import org.quartz.*;
import org.quartz.Trigger.TriggerState;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 * 计划任务管理
 *
 * @author DIY
 * 2018年5月16日  下午5:55:52
 */
@Service
public class QuartzManager {
     
    public final Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private Scheduler scheduler;

    /**
     * 添加任务
     *
     * @param job
     */
    public void addJob(ScheduleJob job) throws Exception {
     
        // 创建jobDetail实例,绑定Job实现类
        // 指明job的名称,所在组的名称,以及绑定job类
        JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class)
                .withIdentity(job.getJobKey())
			  //.withIdentity(job.getJobName(), job.getJobGroup()) //和上面2选1,效果一样,获取定时器名称和分组,是识别每一个定时器任务的key
                .usingJobData(job.getDataMap()) //这里就可以获取到每个定时器的动态入参实体类,重要!
                .build();
        // 定义调度触发规则
        // 使用cornTrigger规则
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(job.getJobName(), job.getJobGroup())// 触发器key
                .withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
                .startNow()
                .build();
        // 把作业和触发器注册到任务调度中
        scheduler.scheduleJob(jobDetail, trigger);
        // 判断调度器是否启动
        if (!scheduler.isStarted()) {
     
            scheduler.start();
        }
        log.info(String.format("定时任务:%s.%s-已添加到调度器!", job.getJobGroup(), job.getJobName()));

    }

    /**
     * 获取所有计划中的任务列表
     *
     * @return
     */
    public List<ScheduleJob> getAllJob() throws SchedulerException {
     
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        List<ScheduleJob> jobList = new ArrayList<ScheduleJob>();
        for (JobKey jobKey : jobKeys) {
     
            ScheduleJob job = ScheduleJob.getJobFromMap(scheduler.getJobDetail(jobKey).getJobDataMap());
            jobList.add(job);
        }
        return jobList;
    }

    /**
     * 所有正在运行的job
     *
     * @return
     */
    public List<ScheduleJob> getRunningJob() throws SchedulerException {
     
        List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
        List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size());
        for (JobExecutionContext executingJob : executingJobs) {
     
            ScheduleJob job = ScheduleJob.getJobFromMap(executingJob.getJobDetail().getJobDataMap());
            jobList.add(job);
        }
        return jobList;
    }

    /**
     * 暂停一个job
     *
     * @param job
     */
    public void pauseJob(ScheduleJob job) throws SchedulerException {
     
        if (scheduler.checkExists(job.getJobKey())) {
     
            scheduler.pauseJob(job.getJobKey());
            log.info(String.format("定时任务:%s.%s-已暂停!", job.getJobGroup(), job.getJobName()));
        } else {
     
            log.debug("Failed pause exist DynamicJob {}, because not fount JobKey [{}]", job, job.getJobKey());
        }
    }

    /**
     * 恢复一个job
     *
     * @param job
     */
    public void resumeJob(ScheduleJob job) throws SchedulerException {
     
        if (scheduler.checkExists(job.getJobKey())) {
     
            scheduler.resumeJob(job.getJobKey());
            log.info(String.format("定时任务:%s.%s-已重启!", job.getJobGroup(), job.getJobName()));
        } else {
     
            log.debug("Failed pause exist DynamicJob {}, because not fount JobKey [{}]", job, job.getJobKey());
        }
    }

    /**
     * 删除一个job
     *
     * @param job
     */
    public void deleteJob(ScheduleJob job) throws SchedulerException {
     
        if (scheduler.checkExists(job.getJobKey())) {
     
            scheduler.deleteJob(job.getJobKey());
            log.info(String.format("定时任务:%s.%s-已删除!", job.getJobGroup(), job.getJobName()));
        } else {
     
            log.debug("Failed pause exist DynamicJob {}, because not fount JobKey [{}]", job, job.getJobKey());
        }
    }

    /**
     * 立即执行job
     *
     * @param job
     */
    public void runAJobNow(ScheduleJob job) throws SchedulerException {
     
        if (scheduler.checkExists(job.getJobKey())) {
     
            scheduler.triggerJob(job.getJobKey());
            log.info(String.format("定时任务:%s.%s-立即启动!", job.getJobGroup(), job.getJobName()));
        } else {
     
            log.debug("Failed pause exist DynamicJob {}, because not fount JobKey [{}]", job, job.getJobKey());
        }

    }

    /**
     * 更新job时间表达式
     *
     * @param job
     * @param jobName
     * @param jobGroup
     * @return
     */
    public void updateJobCron(ScheduleJob job, String jobName, String jobGroup) throws SchedulerException {
     

        TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());

        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()).withMisfireHandlingInstructionDoNothing();
        trigger = (CronTrigger) trigger.getTriggerBuilder().withIdentity(triggerKey)
                .usingJobData(job.getDataMap())
                .withSchedule(scheduleBuilder).build();
        scheduler.rescheduleJob(triggerKey, trigger);

        log.info(String.format("定时任务:%s.%s-更换新的执行时间[" + job.getCronExpression() + "]!", job.getJobGroup(), job.getJobName()));
    }

    public TriggerState jobIsRun(ScheduleJob job) {
     
        TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
        try {
     
            return scheduler.getTriggerState(triggerKey);
        } catch (SchedulerException e) {
     
            e.printStackTrace();
        }
        return null;
    }
}

这个是定时器实体类,用来存储定时器的动态配置参数

@Data
@Component
public class ScheduleJob implements Serializable, Job {
     

    @Autowired
    private TrackTopicsService trackTopicsService;

    private static ScheduleJob scheduleJob;

    @PostConstruct
    private void init(){
     
        scheduleJob = this;
        scheduleJob.trackTopicsService = this.trackTopicsService;
    }

    private static final long serialVersionUID = 1L;
    private static final String JOB_MAP_KEY = "self";
    public static final String STATUS_RUNNING = "1";
    public static final String STATUS_NOT_RUNNING = "0";
    public static final String CONCURRENT_IS = "1";
    public static final String CONCURRENT_NOT = "0";

    /**
     * 任务名称
     */
    private String jobName;
    /**
     * 任务分组
     */
    private String jobGroup;
    /**
     * cron表达式
     */
    private String cronExpression;
    /**
     * 订单id
     */
    private Integer pid;
    /**
     * 用户id
     */
    private String uid;
    /**
     * 订单关键词
     */
    private String keywords;
    /**
     * 收件人
     */
    private String toAddress;
    /**
     * 追踪频率
     */
    private String frequency;
    /**
     * 创建时间
     */
    @JSONField(format = "yyyy-MM-dd HH:mm:ss")
    private String createDate;
    /**
     * 更新时间
     */
    @JSONField(format = "yyyy-MM-dd HH:mm:ss")
    private String updateDate;
    /**
     * 标志位(用来判断是新增还是更新)
     */
    private String flag;


    @JsonIgnore
    private JobDataMap dataMap = new JobDataMap();


    public JobDataMap getDataMap() {
     
        if (dataMap.size() == 0) {
     
            dataMap.put(JOB_MAP_KEY, this);
        }
        return dataMap;
    }


    public JobKey getJobKey() {
     
        return JobKey.jobKey(jobName, jobGroup);// 任务名称和组构成任务key
    }

    public static ScheduleJob getJobFromMap(JobDataMap dataMap) {
     
        return (ScheduleJob) dataMap.get(JOB_MAP_KEY);
    }
	//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
	//该实现后的方法是定时器的入口
    //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    @Override
    @Transactional(rollbackFor = {
     Throwable.class})
    public void execute(JobExecutionContext context) throws JobExecutionException {
     
        System.out.println("定时器执行:START>>>>>>>>>>>>>>>>>");

        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        System.out.println("当前执行JOB:" + dataMap.get(JOB_MAP_KEY));
        //每一个转换后的定时器实体类都会获取到执行前设置的参数
        ScheduleJob sj = (ScheduleJob) dataMap.get(JOB_MAP_KEY);
        //这里将定时要执行的具体任务放到service层集中管理
        scheduleJob.trackTopicsService.quartzJob(sj);

        System.out.println("定时器执行:OVER>>>>>>>>>>>>>>>>>>");
    }

    @Override
    public String toString() {
     
        return "ScheduleJob{" +
                "jobName='" + jobName + '\'' +
                ", jobGroup='" + jobGroup + '\'' +
                ", jobStatus='" + jobStatus + '\'' +
                ", cronExpression='" + cronExpression + '\'' +
                ", description='" + description + '\'' +
                ", beanClass='" + beanClass + '\'' +
                ", isConcurrent='" + isConcurrent + '\'' +
                ", springBean='" + springBean + '\'' +
                ", methodName='" + methodName + '\'' +
                ", pid='" + pid + '\'' +
                ", keywords='" + keywords + '\'' +
                ", toAddress='" + toAddress + '\'' +
                ", frequency='" + frequency + '\'' +
                ", trackAmount='" + trackAmount + '\'' +
                ", createDate='" + createDate + '\'' +
                ", updateDate='" + updateDate + '\'' +
                ", flag='" + flag + '\'' +
                '}';
    }
}

3.接下来是Controller,Service,Dao三层,底层sql编写用的Mybatis,是基础的增删改查,现避免文章过于冗长,只放Service层实现代码,具体可至下方拉取gitee主页项目源码

@Service
public class TrackTopicsServiceImpl implements TrackTopicsService {
     

    @Autowired
    private TrackTopicsDao trackTopicsDao;

    @Autowired
    private QuartzManager quartzManager; 

    @Override
    @Transactional(rollbackFor = {
     Throwable.class})
    public String quartzJob(ScheduleJob sj) {
     
        /**
     	 * 这里是定时器执行具体任务实现
     	 * 我这里主要操作是调取热文获取接口,再调用邮件发送接口,最后将发送记录也保存到数据库,包括定时
     	 * 任务信息,cron表达式,发送成功与否code和msg等字段
     	 * 
         */
		...
		...
    }

	/**
     * 修改订单
     */
    @Override
    public int editPushOrder(PushOrder po) throws Exception {
     
        ..
		..
        // 任务名称
        String jobName = UUID.randomUUID().toString();
        // 任务所属分组
        String jobGroup = ScheduleJob.class.getName();
        //将原定时器删除再添加,本人试过job的更新方法,定时器信息存储至数据库时无法同步更新,固放弃
        if (!(p.getJobName().equals("") && p.getJobGroup().equals(""))) {
     
            quartzManager.deleteJob(job);
        }
        job.setJobName(jobName);
        job.setJobGroup(jobGroup);
        quartzManager.addJob(job);
        poi.setJobName(jobName);
        poi.setJobGroup(jobGroup);
        trackTopicsDao.editPushOrder(poi);

        return count;
    }

	/**
     * 删除订单
     */
    @Override
    public int delPushOrder(Integer pid) throws Exception {
     
        R r = null;
        int count = 0;
        //删除订单记录前取出job信息,需操作对应定时任务
        List<PushOrder> list = trackTopicsDao.getOwnPushOrders("", String.valueOf(pid));
        PushOrder p = list.get(0);

        count = trackTopicsDao.delPushOrder(pid);
        if (count > 0) {
     
            ScheduleJob sj = new ScheduleJob();
            sj.setJobName(p.getJobName());
            sj.setJobGroup(p.getJobGroup());
            quartzManager.deleteJob(sj);
        }
        return count;
    }

	/**
     * 添加订单
     */
    @Override
    public int addPushOrder(PushOrder po) throws Exception {
     
        R r = null;
        int count = 0;
        Integer pid = 0;
        // 任务名称
        String jobName = UUID.randomUUID().toString();
        // 任务所属分组
        String jobGroup = ScheduleJob.class.getName();
        po.setJobName(jobName);
        po.setJobGroup(jobGroup);
        ScheduleJob job = Utils.initScheduleJob(po, "1");
        po.setCronExpression(job.getCronExpression());
        //在一开始就插入表达式,任务名称等字段是存在一个问题,如果订单生成,定时器还未触发
        //更新订单表这些信息时,服务停止或重启,则之前订单的job信息会丢失
        count = trackTopicsDao.addPushOrder(po);
        //添加订单成功,准备进入定时器初始化
        if (count > 0) {
     
            pid = po.getPid();
            job.setPid(pid);
            quartzManager.addJob(job);
        }
        return count;
    }

    @Override
    public void runAllJobs() throws Exception {
     

        List<PushOrder> list = trackTopicsDao.getAllJobs();
        System.out.println("<<<<<<<<<<<<<<" + "定时器初始化开始" + ">>>>>>>>>>>>>>>");
        for (PushOrder po : list) {
     
            ScheduleJob job = new ScheduleJob();
            job = Utils.initScheduleJob(po, "3");
            quartzManager.addJob(job);
        }
        System.out.println("<<<<<<<<<<<<<<" + "定时器初始化结束" + ">>>>>>>>>>>>>>>");

    }
}

4.再附上启动类配置

@SpringBootApplication
@MapperScan("io.metstr.dao")
@EnableTransactionManagement
@EnableScheduling
public class DevisePartnersApplication implements CommandLineRunner {
     

	@Autowired
	private TrackTopicsService trackTopicsService;

	public static void main(String[] args) {
     
		SpringApplication.run(DevisePartnersApplication.class, args);
	}

	//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
	//该方法是服务启动时,自动启动仍需要被执行的定时器,注意上方要实现CommandLineRunner接口,重要!
    //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
	@Override
	public void run(String... args) throws Exception {
     
		trackTopicsService.runAllJobs();
	}
}

  • 最后再附上示例源码地址:点我前往,如有问题可以随时指出,共同学习进步

你可能感兴趣的:(quartz定时器,java,quartz)