SpringBoot 2.2.5 整合Quartz,配置动态增删改查定时任务,并解决Job中不能注入Bean问题

前言:该博客主要是记录自己学习的过程,方便以后查看,当然也希望能够帮到大家。

由于工作上的需要,初步了解Quartz后进行使用。完整代码地址在结尾!!

第一步,在pom.xml文件中加入依赖,如下

<!-- Quartz -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

第二步,自定义JobFactory类

注意:我注入了一个 自定义的JobFactory ,然后把它设置为SchedulerFactoryBean的JobFactory。让其在具体job类实例化时使用spring的api来进行依赖注入,目的是因为我在具体的job中需要注入一些spring的bean。
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

/**
 * @version 1.0
 * @author jinhaoxun
 * @date 2018-05-09
 * @description Quartz创建JobFactory实例
 */
@Component
public class JobFactory extends AdaptableJobFactory {

    /**
     * AutowireCapableBeanFactory接口是BeanFactory的子类
     * 可以连接和填充那些生命周期不被Spring管理的已存在的bean实例
     */
    private AutowireCapableBeanFactory factory;

    /**
     * @author jinhaoxun
     * @description 构造器
     * @param factory
     */
    public JobFactory(AutowireCapableBeanFactory factory) {
        this.factory = factory;
    }

    /**
     * @author  jinhaoxun
     * @description 创建Job实例
     * @param bundle
     * @return Object
     */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object job = super.createJobInstance(bundle);// 实例化对象
        factory.autowireBean(job);// 进行注入(Spring管理该Bean)
        return job;//返回对象
    }
}

第三步,自定义QuartzConfig配置文件

import lombok.extern.slf4j.Slf4j;
import org.quartz.Scheduler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

/**
 * @version 1.0
 * @author jinhaoxun
 * @date 2018-05-09
 * @description Quartz配置
        */
@Slf4j
@Configuration
public class QuartzConfig {

    private JobFactory jobFactory;

    /**
     * @author jinhaoxun
     * @description 构造器
     * @param jobFactory
     */
    public QuartzConfig(JobFactory jobFactory){
        this.jobFactory = jobFactory;
    }

    /**
     * @author  jinhaoxun
     * @description 配置SchedulerFactoryBean,将一个方法产生为Bean并交给Spring容器管理
     * @return SchedulerFactoryBean
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        log.info("开始注入定时任务调度器工厂...");
        SchedulerFactoryBean factory = new SchedulerFactoryBean();// Spring提供SchedulerFactoryBean为Scheduler提供配置信息,并被Spring容器管理其生命周期
        factory.setJobFactory(jobFactory);// 设置自定义Job Factory,用于Spring管理Job bean
        log.info("注入定时任务调度器工厂成功!");
        return factory;
    }

    @Bean(name = "scheduler")
    public Scheduler scheduler() {
        return schedulerFactoryBean().getScheduler();
    }
}

第四步,新增任务请求实体类AddSimpleJobReq,AddCronJobReq,DeleteJobReq,如下

AddSimpleJobReq
import lombok.Data;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description: 新增Simple定时任务请求实体类
 * @Author: jinhaoxun
 * @Date: 2020/1/15 11:20
 * @Version: 1.0.0
 */
@Data
public class AddSimpleJobReq {

    private String jobClass;

    private Date date;

    private Map<String, String> params = new HashMap<>();

}
AddCronJobReq
import lombok.Data;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description: 新增Cron定时任务请求实体类
 * @Author: jinhaoxun
 * @Date: 2020/1/15 11:20
 * @Version: 1.0.0
 */
@Data
public class AddCronJobReq {

    private String jobName;

    private String jobGroupName;

    private String triggerName;

    private String triggerGroupName;

    private String jobClass;

    private String date;

    private Map<String, String> params = new HashMap<>();

}
DeleteJobReq
import lombok.Data;

/**
 * @Description: 删除定时任务请求实体类
 * @Author: jinhaoxun
 * @Date: 2020/1/15 11:20
 * @Version: 1.0.0
 */
@Data
public class DeleteJobReq {

    private String jobName;

    private String jobGroupName;

    private String triggerName;

    private String triggerGroupName;

}

第五步,自定义QuartzManager类,用于操作定时任务

import com.jinhaoxun.quartz.entity.request.AddCronJobReq;
import com.jinhaoxun.quartz.entity.request.AddSimpleJobReq;
import com.jinhaoxun.quartz.entity.request.DeleteJobReq;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.stereotype.Service;

import static org.quartz.DateBuilder.futureDate;

/**
 * @Description: Quartz管理操作类
 * @Author: jinhaoxun
 * @Date: 2020/1/15 11:20
 * @Version: 1.0.0
 */
@Slf4j
@Service
public class QuartzManager {

    private Scheduler scheduler;

    /**
     * @author jinhaoxun
     * @description 构造器
     * @param scheduler 调度器
     */
    public QuartzManager(Scheduler scheduler){
        this.scheduler = scheduler;
    }

    /**
     * quartz任务类包路径
     */
    private static String jobUri = "com.jinhaoxun.quartz.job.";

    /**
     * @author jinhaoxun
     * @description 添加一个Simple定时任务,只执行一次的定时任务
     * @param addSimpleJobReq 参数对象
     * @param taskId 任务ID,不能同名
     * @throws RuntimeException
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public void addSimpleJob(AddSimpleJobReq addSimpleJobReq, String taskId) throws Exception {
        String jobUrl = jobUri + addSimpleJobReq.getJobClass();
        try {
            Class<? extends Job> aClass = (Class<? extends Job>) Class.forName(jobUrl).newInstance().getClass();
            // 任务名,任务组,任务执行类
            JobDetail job = JobBuilder.newJob(aClass).withIdentity(taskId,
                    "JobGroup").build();
            //增加任务ID参数
            addSimpleJobReq.getParams().put("taskId",taskId);
            // 添加任务参数
            job.getJobDataMap().putAll(addSimpleJobReq.getParams());
            // 转换为时间差,秒单位
            int time = (int) (addSimpleJobReq.getDate().getTime() - System.currentTimeMillis()) / 1000;

            SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
                    .withIdentity(taskId, taskId + "TiggerGroup")
                    .startAt(futureDate(time, DateBuilder.IntervalUnit.SECOND))
                    .build();
            // 调度容器设置JobDetail和Trigger
            scheduler.scheduleJob(job, trigger);
            if (!scheduler.isShutdown()) {
                // 启动
                scheduler.start();
            }
        } catch (Exception e) {
            log.info("Quartz新增任务失败");
        }
    }

    /**
     * @author jinhaoxun
     * @description 添加一个Cron定时任务,循环不断执行的定时任务
     * @param addCronJobReq 参数对象
     * @throws Exception
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public void addCronJob(AddCronJobReq addCronJobReq) throws Exception {
        String jobUrl = jobUri + addCronJobReq.getJobClass();
        try {
            Class<? extends Job> aClass = (Class<? extends Job>) Class.forName(jobUrl).newInstance().getClass();
            // 任务名,任务组,任务执行类
            JobDetail job = JobBuilder.newJob(aClass).withIdentity(addCronJobReq.getJobName(),
                    addCronJobReq.getJobGroupName()).build();
            // 添加任务参数
            job.getJobDataMap().putAll(addCronJobReq.getParams());
            // 创建触发器
            CronTrigger trigger = (CronTrigger) TriggerBuilder.newTrigger()
                    // 触发器名,触发器组
                    .withIdentity(addCronJobReq.getTriggerName(), addCronJobReq.getTriggerGroupName())
                    // 触发器时间设定
                    .withSchedule(CronScheduleBuilder.cronSchedule(addCronJobReq.getDate()))
                    .build();
            // 调度容器设置JobDetail和Trigger
            scheduler.scheduleJob(job, trigger);

            if (!scheduler.isShutdown()) {
                // 启动
                scheduler.start();
            }
        } catch (Exception e) {
            log.info("Quartz新增任务失败");
        }
    }

    /**
     * @author jinhaoxun
     * @description 修改一个任务的触发时间
     * @param triggerName       触发器名
     * @param triggerGroupName  触发器组名
     * @param cron              时间设置,参考quartz说明文档
     * @throws Exception
     */
    public void modifyJobTime(String triggerName, String triggerGroupName, String cron) throws Exception {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            String oldTime = trigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(cron)) {
                // 触发器
                TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
                // 触发器名,触发器组
                triggerBuilder.withIdentity(triggerName, triggerGroupName);
                triggerBuilder.startNow();
                // 触发器时间设定
                triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
                // 创建Trigger对象
                trigger = (CronTrigger) triggerBuilder.build();
                // 方式一 :修改一个任务的触发时间
                scheduler.rescheduleJob(triggerKey, trigger);
            }
        } catch (Exception e) {
            log.info("Quartz修改任务失败");
        }
    }
    /**
     * @author jinhaoxun
     * @description 移除一个任务
     * @param deleteJobReq     参数对象
     * @throws Exception
     */
    public void removeJob(DeleteJobReq deleteJobReq) throws Exception {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(deleteJobReq.getTriggerName(), deleteJobReq.getTriggerGroupName());
            // 停止触发器
            scheduler.pauseTrigger(triggerKey);
            // 移除触发器
            scheduler.unscheduleJob(triggerKey);
            // 删除任务
            scheduler.deleteJob(JobKey.jobKey(deleteJobReq.getJobName(), deleteJobReq.getJobGroupName()));
        } catch (Exception e) {
            log.info("Quartz删除改任务失败");
        }
    }

    /**
     * @author jinhaoxun
     * @description 获取任务是否存在
     * @param triggerName       触发器名
     * @param triggerGroupName  触发器组名
     * @return Boolean 返回操作结果
     * 获取任务是否存在
     * STATE_BLOCKED 4 阻塞
     * STATE_COMPLETE 2 完成
     * STATE_ERROR 3 错误
     * STATE_NONE -1 不存在
     * STATE_NORMAL 0 正常
     * STATE_PAUSED 1 暂停
     * @throws Exception
     */
    public Boolean notExists(String triggerName, String triggerGroupName) throws Exception {
        try {
            if (scheduler.getTriggerState(TriggerKey.triggerKey(triggerName, triggerGroupName)) == Trigger.TriggerState.NORMAL){
                return true;
            }
        } catch (Exception e) {
            log.info("Quartz获取任务是否存在失败");
        }
        return false;
    }

    /**
     * @author jinhaoxun
     * @description 关闭调度器
     * @throws RuntimeException
     */
    public void shutdown() throws Exception {
        try {
            if(scheduler.isStarted()){
                scheduler.shutdown(true);
            }
        } catch (Exception e) {
            log.info("Quartz关闭调度器失败");
        }
    }

}

第六步,新增自定义任务类型,将要做的操作放到该类的execute方法中,主要该类的路径,要放到上面配置的com.jinhaoxun.quartz.job包里面

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;

/**
 * @Description: Job测试类
 * @Author: jinhaoxun
 * @Date: 2020/1/15 11:20
 * @Version: 1.0.0
 */
@Slf4j
public class JobTest implements Job {

    /**
     * @author jinhaoxun
     * @description 重写任务内容
     * @param jobExecutionContext 设置的key
     * @throws
     */
    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
        //获取参数
        JobDataMap dataMap = jobExecutionContext.getMergedJobDataMap();
        String id = dataMap.getString("id");
        String name = dataMap.getString("name");
        try {
            log.info("执行任务{},{}",id,name);
        } catch (Exception e) {
            log.info("Quartz执行失败");
        }
    }
}

第七步,编写单元测试类,QuartzApplicationTests,并进行测试,使用方法基本都有注释

import com.luoyu.quartz.entity.request.AddCronJobReq;
import com.luoyu.quartz.entity.request.AddSimpleJobReq;
import com.luoyu.quartz.entity.request.DeleteJobReq;
import com.luoyu.quartz.manager.QuartzManager;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
// 获取启动类,加载配置,确定装载 Spring 程序的装载方法,它回去寻找 主配置启动类(被 @SpringBootApplication 注解的)
@SpringBootTest
class QuartzApplicationTests {

    @Autowired
    private QuartzManager quartzManager;

    @Test
    void addSimpleJobTest() throws Exception {
        Map<String, String> params = new HashMap<>();
        params.put("id","测试id");
        params.put("name","测试name");

        Calendar beforeTime = Calendar.getInstance();
        // 5 秒之后的时间
        beforeTime.add(Calendar.SECOND, 5);
        Date beforeDate = beforeTime.getTime();

        AddSimpleJobReq addSimpleJobReq = new AddSimpleJobReq();
        addSimpleJobReq.setDate(beforeDate);
        addSimpleJobReq.setJobClass("JobTest");
        addSimpleJobReq.setParams(params);
        quartzManager.addSimpleJob(addSimpleJobReq, "123");
        // 让主线程睡眠60秒
        Thread.currentThread().sleep(60000);
    }

    @Test
    void addCronJobTest() throws Exception {
        Map<String, String> params = new HashMap<>();
        params.put("id","测试id");
        params.put("name","测试name");
        AddCronJobReq addCronJobReq = new AddCronJobReq();
        //每 5 秒执行一次
        addCronJobReq.setDate("0/5 * * * * ?");
        addCronJobReq.setJobClass("JobTest");
        addCronJobReq.setJobGroupName("JobGroupName");
        addCronJobReq.setJobName("JobName");
        addCronJobReq.setParams(params);
        addCronJobReq.setTriggerGroupName("triggerGroupName");
        addCronJobReq.setTriggerName("triggerName");
        quartzManager.addCronJob(addCronJobReq);
        // 让主线程睡眠60秒
        Thread.currentThread().sleep(60000);
    }

    @Test
    void removeJobTest() throws Exception {
        Map<String, String> params = new HashMap<>();
        params.put("id","测试id");
        params.put("name","测试name");

        Calendar beforeTime = Calendar.getInstance();
        // 5 秒之后的时间
        beforeTime.add(Calendar.SECOND, 5);
        Date beforeDate = beforeTime.getTime();

        AddSimpleJobReq addSimpleJobReq = new AddSimpleJobReq();
        addSimpleJobReq.setDate(beforeDate);
        addSimpleJobReq.setJobClass("JobTest");
        addSimpleJobReq.setParams(params);
        quartzManager.addSimpleJob(addSimpleJobReq, "123");

        DeleteJobReq deleteJobReq = new DeleteJobReq();
        deleteJobReq.setJobName("123");
        deleteJobReq.setJobGroupName("123JobGroup");
        deleteJobReq.setTriggerName("123");
        deleteJobReq.setTriggerGroupName("123TiggerGroup");
        quartzManager.removeJob(deleteJobReq);
        // 让主线程睡眠60秒
        Thread.currentThread().sleep(60000);

    }

    @Test
    void shutdownTest() throws Exception {
        quartzManager.shutdown();
    }

    @BeforeEach
    void testBefore(){
        log.info("测试开始!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    }

    @AfterEach
    void testAfter(){
        log.info("测试结束!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    }

}
完整代码地址:https://github.com/Jinhx128/springboot-demo
注:此工程包含多个module,本文所用代码均在quartz-demo模块下

后记:本次分享到此结束,本人水平有限,难免有错误或遗漏之处,望大家指正和谅解,欢迎评论留言。

(0)

相关推荐