Springboot整合quartz实现动态管理定时任务

目前对于定时功能的需求越来越多,这篇文章描述了如何动态实现定时任务,可以将所有定时任务的属性存储到数据库中,后端编写查询所有定时任务列表(包含无参数限制查询和有参数限制模糊查询),前端调用查询接口可以将所有定时任务显示出来,前端也可以选中某个定时任务,调用后端开启定时任务、关闭定时任务、恢复定时任务、删除定时任务以及修改定时任务刷新周期等接口完成对定时任务的动态处理。


1.创建数据库以及创建定时任务表


2.创建对应的实体类

package com.mhc.common.scheduler;

/**
 * job基本信息
 * Description:
 * User: mhc
 * Date: 2019-06-25
 * Time: 20:46
 */
public class ScheduleJob {
    /** 任务id */
    private String jobId;
    /** 任务名称 */
    private String jobName;
    /** 任务分组 */
    private String jobGroup = "default-group";
    /** 任务状态 0禁用 1启用 2删除*/
    private String jobStatus = JobStatus.RUNNING;
    /** 任务运行时间表达式 */
    private String cronExpression;
    /** 任务描述 */
    private String desc;
    /**任务全类名**/
    private String beanClass;

    public ScheduleJob(){}

    public ScheduleJob(String jobId, String jobName, String jobGroup, String jobStatus, String cronExpression, String desc) {
        this.jobId = jobId;
        this.jobName = jobName;
        this.jobGroup = jobGroup;
        this.jobStatus = jobStatus;
        this.cronExpression = cronExpression;
        this.desc = desc;
    }

    public ScheduleJob(String jobId, String jobName, String cronExpression) {
        this.jobId = jobId;
        this.jobName = jobName;
        this.cronExpression = cronExpression;
    }

    public ScheduleJob(String jobId, String jobName) {
        this.jobId = jobId;
        this.jobName = jobName;
    }

     


             3.创建定时任务状态接口

package com.mhc.common.scheduler;

/**
 * Job状态
 */
public interface JobStatus {
    /**禁用*/
    String STOPED="0";
    /**启用*/
    String RUNNING="1";
    /**删除*/
    String DELETED="2";
}

         4.定义配置类

配置类主要定义三个对象:

        (1)定时任务jobDetail:只要用来封装你要做什么,执行什么业务操作。

       (2)定时任务触发器jobTrigger:主要将定时任务jobDetail封装,同时封装cron表达式,说明什么时间触发定时任务或者多长时间触发刷新一次定时任务。

     (3)quartz调度工厂scheduler:主要将jobTrigger触发器封装,调用自己的api执行定时任务

package com.mhc.common.scheduler;

import org.quartz.Trigger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: mhc
 * Date: 2019-06-25
 * Time: 21:10
 */
@Configuration
public class QuartzConfigration {

    /**
     * 配置定时任务
     * @param task
     * @return
     */
    @Bean(name = "jobDetail")
    public MethodInvokingJobDetailFactoryBean detailFactoryBean(ScheduleDefaultTask task) {
        MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
         //是否并发执行即是否等待当前未执行完的任务
        jobDetail.setConcurrent(false);

        jobDetail.setName("default");// 设置任务的名字
        jobDetail.setGroup("default-group");// 设置任务的分组,这些属性都可以存储在数据库中,在多任务的时候使用
        jobDetail.setTargetObject(task);
        jobDetail.setTargetMethod("defaultExecute");
        return jobDetail;
    }

    /**
     * 配置定时任务的触发器
     * @param jobDetail
     * @return
     */
    @Bean(name = "jobTrigger")
    public CronTriggerFactoryBean cronJobTrigger(MethodInvokingJobDetailFactoryBean jobDetail) {
        CronTriggerFactoryBean tigger = new CronTriggerFactoryBean();
        tigger.setJobDetail(jobDetail.getObject());
        tigger.setCronExpression("0 0 0 1/1 * ?");// 初始时的cron表达式
        tigger.setName("default-tigger");// trigger的name
        return tigger;

    }

    /**
     * quartz调度工厂
     * @param cronJobTrigger
     * @return
     */
    @Bean(name = "scheduler")
    public SchedulerFactoryBean schedulerFactory(Trigger cronJobTrigger,MyAdaptableJobFactory myAdaptableJobFactory) {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setOverwriteExistingJobs(true);
        bean.setStartupDelay(1);
        bean.setTriggers(cronJobTrigger);
        bean.setJobFactory(myAdaptableJobFactory);
        return bean;
    }
}

         5.实现创建、开启、关闭、恢复、删除、修改刷新周期定时任务的具体操作

package com.mhc.common.scheduler;

import com.mhc.common.bean.ResponseCodes;
import com.mhc.common.bean.Result;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: mhc
 * Date: 2019-06-25
 * Time: 21:14
 */
@Component
public class SchedulerBuilder {

    @Resource(name = "scheduler")
    private Scheduler scheduler;

    /**
     * 新建job
     * @param job
     * @param clazz
     * @return
     */
    public Result createJob(ScheduleJob job, Class clazz) {
        Result res = new Result();
        res = validateScheduleJob(job);
        if(res.getResultCode()!=ResponseCodes.SUCCESS){
            return res;
        }
        try {
            if (!((clazz.newInstance()) instanceof Job)){
                res = new Result(ResponseCodes.IllegalArgument,clazz.getName()+"必须实现Job接口!");
                return res;
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());

        //获取trigger
        CronTrigger trigger = null;
        try {
            trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }

        //不存在,创建一个
        if (null == trigger) {
            JobDetail jobDetail = JobBuilder.newJob(clazz)
                    .withIdentity(job.getJobName(), job.getJobGroup()).build();
            jobDetail.getJobDataMap().put("scheduleJob", job);

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

            //按新的cronExpression表达式构建一个新的trigger
            trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup()).withSchedule(scheduleBuilder).build();

            try {
                scheduler.scheduleJob(jobDetail, trigger);
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
        }else{
            res = new Result(ResponseCodes.AlreadyExists,"job已经存在,请检查名称是否重复");
        }
        return res;
    }

    /**
     * 立即执行job
     * @param job
     * @return
     */
    public Result executeJob(ScheduleJob job) {
        Result res = new Result();
        res = validateScheduleJob(job);
        if(res.getResultCode()!=ResponseCodes.SUCCESS){
            return res;
        }
        JobKey jobKey = JobKey.jobKey(job.getJobName(), job.getJobGroup());
        try {
            scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * 更新执行时间
     * @param job
     * @return
     */
    public Result updateJobCron(ScheduleJob job) {
        Result res = new Result();
        res = validateScheduleJob(job);
        if(res.getResultCode()!=ResponseCodes.SUCCESS){
            return res;
        }
        TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(),
                job.getJobGroup());

        //获取trigger,即在spring配置文件中定义的 bean id="myTrigger"
        CronTrigger trigger = null;
        try {
            trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }

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

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

        //按新的trigger重新设置job执行
        try {
            scheduler.rescheduleJob(triggerKey, trigger);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * 暂停job运行
     * @param job
     * @return
     */
    public Result pauseJob(ScheduleJob job) {
        Result res = new Result();
        res = validateScheduleJob(job);
        if(res.getResultCode()!=ResponseCodes.SUCCESS){
            return res;
        }
        JobKey jobKey = JobKey.jobKey(job.getJobName(), job.getJobGroup());
        try {
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * 恢复job运行
     * @param job
     * @return
     */
    public Result resumeJob(ScheduleJob job) {
        Result res = new Result();
        res = validateScheduleJob(job);
        if(res.getResultCode()!=ResponseCodes.SUCCESS){
            return res;
        }
        JobKey jobKey = JobKey.jobKey(job.getJobName(), job.getJobGroup());
        try {
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * 删除job信息
     * @param job
     * @return
     */
    public Result delJob(ScheduleJob job) {
        Result res = new Result();
        res = validateScheduleJob(job);
        if(res.getResultCode()!=ResponseCodes.SUCCESS){
            return res;
        }
        JobKey jobKey = JobKey.jobKey(job.getJobName(), job.getJobGroup());
        TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(),
                job.getJobGroup());
        try {
            scheduler.pauseTrigger(triggerKey);// 停止触发器
            scheduler.unscheduleJob(triggerKey);// 移除触发器
            scheduler.deleteJob(jobKey);//删除job
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * 验证job基本信息
     * @param job
     * @return
     */
    private Result validateScheduleJob(ScheduleJob job){
        Result res = new Result();
        if(job==null) {
            res = new Result(ResponseCodes.IllegalArgument,"job 不能为空!");
            return res;
        }
        if (StringUtils.trimToNull(job.getJobName())==null){
            res = new Result(ResponseCodes.IllegalArgument,"job名称不能为空!");
            return res;
        }
        return res;
    }
}

        6.创建一个定时任务demo(必须实现Job接口,重写execute方法)在execute方法体中写需要执行定时操作的业务代码,这里只是简单在控制台打印一句话。

package com.mhc.common.scheduler;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
import java.util.Date;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: mhc
 * Date: 2019-06-25
 * Time: 21:20
 */
@Component
public class ScheduleDefaultTask implements Job {

    public void defaultExecute(){
        System.out.println("default task");
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("任务成功运行"+new Date());
    }
}

           7.创建controller

package com.mhc.controller;

import com.mhc.common.scheduler.ScheduleJob;
import com.mhc.common.scheduler.SchedulerBuilder;
import com.mhc.dao.ScheduleDao;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: mhc
 * Date: 2019-06-25
 * Time: 21:25
 */
@RestController
@RequestMapping("/schedule")
public class ScheduleController {

    @Resource
    SchedulerBuilder schedulerBuilder;

    @Autowired
    private ScheduleDao scheduleDao;

    /**
     * 查询定时任务列表
     * @param desc
     * @return
     */
    @PostMapping("/queryJobList")
    public List<ScheduleJob> queryJobList(@RequestParam(required = false,value = "desc")String desc){
       //未考虑分页
        return  scheduleDao.queryJobList(desc);
    }
    /**
     * 创建job
     *
     * @param jobId
     * @return
     */
    @RequestMapping(value = "/createJob")
    public String  createJob(@RequestParam(required = true,value = "jobId")String jobId) {
        //根据jobId从数据库查询对应的job对象
        ScheduleJob job=scheduleDao.queryJobByjobId(jobId);
        String taskClass = job.getBeanClass();
        String jobName=job.getJobName();
        //新建对应的job对象
        job = new ScheduleJob(jobId, jobName, taskClass);
        try {
            /*将job对象传递给schedulerBuilder,同时将从数据库中查到的
            此定时任务对应的全包名+类名通过反射得到对应的接口作为参数传递*/
            schedulerBuilder.createJob(job, Class.forName(taskClass));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return "success";
    }

    /**
     * 执行job
     *
     * @param jobId
     * @return
     */
    @RequestMapping(value = "/executeJob")
    public String executeJob(@RequestParam(required = true,value = "jobId")String jobId) {
        ScheduleJob job=scheduleDao.queryJobByjobId(jobId);
        String jobName=job.getJobName();
        job = new ScheduleJob(jobId, jobName);
        schedulerBuilder.executeJob(job);
        return "success";
    }

    /**
     * 更新job执行周期
     *
     * @param jobId cronExpression
     * @return
     */
    @RequestMapping(value = "/updateJobCron")
    public String updateJobCron(@RequestParam(required = true,value = "jobId")String jobId,
                                @RequestParam(required = true,value = "cronExpression")String cronExpression) {
        ScheduleJob job=scheduleDao.queryJobByjobId(jobId);
        String jobName=job.getJobName();
        job = new ScheduleJob(jobId, jobName, cronExpression);
        schedulerBuilder.updateJobCron(job);
        return "success";
    }

    /**
     * 暂停job执行
     *
     * @param jobId
     * @return
     */
    @RequestMapping(value = "/pauseJob")
    public String pauseJob(@RequestParam(required = true,value = "jobId")String jobId) {
        ScheduleJob job=scheduleDao.queryJobByjobId(jobId);
        String jobName=job.getJobName();
        job = new ScheduleJob(jobId, jobName);
        schedulerBuilder.pauseJob(job);
        //修改jobStatus状态
        scheduleDao.updatePauseFlag(jobId);
        return "success";
    }

    /**
     * 恢复job执行
     *
     * @param jobId
     * @return
     */
    @RequestMapping(value = "/resumeJob")
    public String resumeJob(@RequestParam(required = true,value = "jobId")String jobId) {
        ScheduleJob job=scheduleDao.queryJobByjobId(jobId);
        String jobName=job.getJobName();
        job = new ScheduleJob(jobId, jobName);
        schedulerBuilder.resumeJob(job);
        //修改jobStatus状态
        scheduleDao.updateStartFlag(jobId);
        return "success";
    }

    /**
     * 删除job执行
     *
     * @param jobId
     * @return
     */
    @RequestMapping(value = "/delJob")
    public String delJob(@RequestParam(required = true,value = "jobId")String jobId) {
        ScheduleJob job=scheduleDao.queryJobByjobId(jobId);
        String jobName=job.getJobName();
        job = new ScheduleJob(jobId, jobName);
        schedulerBuilder.delJob(job);
        //修改jobStatus状态
        scheduleDao.updateDeleteFlag(jobId);
        return "success";
    }

}

          8.由于没有具体的业务逻辑,省略了service层,在Controller层直接调用dao。

package com.mhc.dao;

import com.mhc.common.scheduler.ScheduleJob;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface ScheduleDao {

    public ScheduleJob queryJobByjobId(@Param("jobId") String jobId);

    public List<ScheduleJob> queryJobList(@Param("desc")String desc);

    public void updateStartFlag(@Param("jobId")String jobId);

    public void updatePauseFlag(@Param("jobId")String jobId);

    public void updateDeleteFlag(@Param("jobId")String jobId);
}

           9.创建对应的数据库操作.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mhc.dao.ScheduleDao">
    <resultMap id="scheduleJobId" type="com.mhc.common.scheduler.ScheduleJob">
        <result property="jobId" column="jobId" />
        <result property="jobName" column="jobName" />
        <result property="jobGroup" column="jobGroup" />
        <result property="cronExpression" column="cronExpression" />
        <result property="desc" column="desc" />
        <result property="jobStatus" column="jobStatus" />
        <result property="beanClass" column="beanClass" />
    </resultMap>

    <!--根据jobId查询对应的job对象-->
    <select id="queryJobByjobId" resultType="com.mhc.common.scheduler.ScheduleJob" parameterType="String">
        select * from t_schedule_job
        where 1=1
        <if test="jobId != null and jobId!=''" >
            and jobId =#{jobId}
        </if>
    </select>

    <!--查询定时任务列表-->
    <select id="queryJobList" resultType="com.mhc.common.scheduler.ScheduleJob" parameterType="String">
        select * from t_schedule_job
        where jobStatus!=2
        <if test="desc != null and desc!=''" >
            and 'desc' like contat('%',#{desc},'%')
        </if>
    </select>

    <!--修改定时任务开启状态-->
    <update id="updateStartFlag" parameterType="String">
        update t_schedule-job
        set
        jobStatus=1
        where 1=1
        <if test="jobId != null and jobId!=''" >
            and jobId =#{jobId}
        </if>
    </update>

    <!--修改定时任务关闭状态-->
    <update id="updatePauseFlag" parameterType="String">
        update t_schedule-job
        set
        jobStatus=0
        where 1=1
        <if test="jobId != null and jobId!=''" >
            and jobId =#{jobId}
        </if>
    </update>

    <!--修改定时任务删除状态-->
    <update id="updateDeleteFlag" parameterType="String">
        update t_schedule-job
        set
        jobStatus=2
        where 1=1
        <if test="jobId != null and jobId!=''" >
            and jobId =#{jobId}
        </if>
    </update>

</mapper>

          10.目前定时任务基本完成,但仍存在bug,例如在第五步创建定时任务的demo中,如果execute中执行的具体定时业务不是简单的在控制台打印一句话,而是需要调用其他类的方法,当然就需要在execute方法外使用@AutoWired注解注入对应的对象,在execute方法体中通过注入的对象调用其方法,完成最终的业务实现。但由于quartz调度工厂在处理定时任务时,通过定时任务的全包名+类名通过反射找到对应的接口,不加载这个类,直接执行execute方法,因此execute方法体中的其他类的对象为null。程序会报空指针异常,因此需要创建一个MyAdaPtaableJobFactory工厂类将对象注入进去。(因此要将此工厂的对象封装到上面第四步配置quartz调度工厂的构造方法中,在第四步对应的代码中已封装进去)

package com.mhc.common.scheduler;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component("myAdaptableJobFactory")
public class MyAdaptableJobFactory extends AdaptableJobFactory {

	//AutowireCapableBeanFactory 可以将一个对象添加到SpringIOC容器中,并且完成该对象注入
	@Autowired
	private AutowireCapableBeanFactory autowireCapableBeanFactory;
	
	/**
	 * 该方法需要将实例化的任务对象手动的添加到springIOC容器中并且完成对象的注入
	 */
	@Override
	protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
		Object obj = super.createJobInstance(bundle);
		//将obj对象添加Spring IOC容器中,并完成注入
		this.autowireCapableBeanFactory.autowireBean(obj);
		return obj;
	}
}

    如果自己可以编写前端页面,可以自己测试后端接口是否有效。

    如果习惯前后端分离,可以使用postman调式程序是否正确。

    如果上述程序存在问题或还有可以优化的地方以及有更好的方法实现,请多多指教,感激不尽。 

 

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值