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;
    /**
     * 收件人地址(例:123456@qq.com)
     */
    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();
	}
}

  • 最后再附上示例源码地址:点我前往,如有问题可以随时指出,共同学习进步
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
1. 引入依赖 在 `pom.xml` 文件中引入以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> ``` 2. 配置数据源 在 `application.yml` 或 `application.properties` 文件中配置数据源: ``` spring.datasource.url=jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ``` 3. 配置Quartz 在 `application.yml` 或 `application.properties` 文件中配置Quartz: ``` # 配置Quartz spring.quartz.job-store-type=jdbc spring.quartz.jdbc.initialize-schema=always spring.quartz.properties.org.quartz.threadPool.threadCount=5 ``` `spring.quartz.job-store-type` 指定Quartz使用的存储类型,这里配置为 `jdbc` 表示使用数据库存储。 `spring.quartz.jdbc.initialize-schema` 指定Quartz是否需要初始化数据库表,这里配置为 `always` 表示每次启动应用程序都会初始化数据库表。 `spring.quartz.properties` 是Quartz的属性配置,这里配置了线程池的线程数为5。 4. 编写任务类 编写Quartz任务类,实现 `org.quartz.Job` 接口即可。 ``` @Component public class MyJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("Hello, Quartz!"); } } ``` 5. 配置任务调度器 编写任务调度器类,实现 `org.springframework.scheduling.quartz.SchedulerFactoryBean` 接口即可。 ``` @Configuration public class QuartzConfig { @Autowired private DataSource dataSource; @Autowired private MyJob myJob; @Bean public SchedulerFactoryBean schedulerFactoryBean() { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); schedulerFactoryBean.setDataSource(dataSource); // 自定义JobFactory,用于支持Spring的自动注入 AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); jobFactory.setApplicationContext(applicationContext); schedulerFactoryBean.setJobFactory(jobFactory); // 配置JobDetail JobDetail jobDetail = JobBuilder.newJob(myJob.getClass()).withIdentity("myJob").build(); // 配置Trigger SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); trigger.setJobDetail(jobDetail); trigger.setRepeatInterval(5000); trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); // 注册Trigger schedulerFactoryBean.setTriggers(trigger.getObject()); return schedulerFactoryBean; } } ``` 6. 自动注入Spring容器 为了支持Spring的自动注入,需要编写一个自定义的 JobFactory。 ``` public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { private transient AutowireCapableBeanFactory beanFactory; @Override public void setApplicationContext(final ApplicationContext context) { beanFactory = context.getAutowireCapableBeanFactory(); } @Override protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception { final Object job = super.createJobInstance(bundle); beanFactory.autowireBean(job); return job; } } ``` 7. 测试 在需要执行定时任务的方法上添加 `@Scheduled` 注解即可。 ``` @Component public class TestJob { @Scheduled(cron = "0/5 * * * * ?") public void test() { System.out.println("test"); } } ``` 以上是springboot整合quartz的常见配置,可以根据具体需求进行调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

循环听

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值