SpringBoot集成Quartz实现动态定时任务

一、背景

在平常的开发过程中,大家对定时任务肯定都不陌生,比如每天0点自动导出下用户数据,每天早上8点自动发送一封系统的邮件等等。简单的定时任务使用spring自带的 @Scheduled实现即可。但是对于一些特殊的场景下,比如我们想在不重启项目的情况下,动态的修改定时器的运行间隔,将原来每30分钟执行一次的定时任务,动态改为5分钟执行一次,这时候Spring自带的定时器就不太方便了。

二、引入Quartz

“工欲善其事,必先利其器”——Quartz就是我们解决这个问题的利器, Quartz是Apache下开源的一款功能强大的定时任务框架。Quartz官网 对它的描述:
QuartzQuartz是一个功能丰富的开源作业调度库,可以集成到几乎任何Java应用程序中——从最小的独立应用程序到最大的电子商务系统。Quartz可用于创建简单或复杂的调度,以执行数万、数百甚至数万个作业;任务被定义为标准Java组件的作业,这些组件可以执行几乎任何您可以编程让它们执行的任务。Quartz调度器包含许多企业级特性,比如对JTA事务和集群的支持。

在使用Quartz之前,我们要先了解下Quartz的中核心组成:

  • Job 任务,要执行的具体内容,我们自定义的任务类都要实现此Job接口,Job接口中只有一个方法,我们的业务逻辑就在此方法编写,Job的源码如下:
    Job

  • JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略

  • Trigger 触发器,用于定义Job任务何时被触发,以何种方式触发。Trigger接口的关系图如下,最常用的是CronTrigger
    Trigger

  • Scheduler 调度容器,Scheduler负责将Job和Trigger关联起来,统一进行任务调度,一个Scheduler中可以注册多个 Job 和 Trigger。

在这里插入图片描述

三、SpringBoot集成Quartz

接下来我们使用SpringBoot 2.2.0.RELEASE,集成Quartz 2.3.0版本,来创建一个简单的demo。

1.添加maven依赖
 <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
 <dependency>
     <groupId>org.quartz-scheduler</groupId>
     <artifactId>quartz</artifactId>
     <version>2.3.0</version>
</dependency>
2.创建Job任务

此处创建了PrintTimeJob 类并实现Job接口,任务很简单——打印开始时间,然后休眠5s,打印结束时间。

package com.example.demo.quartz;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;


public class PrintTimeJob implements Job {

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-DD HH:mm:ss");
 
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String format = sdf.format(new Date());
        System.out.println(Thread.currentThread().getName()+"  任务开始>>>>>>>>>>>>>>现在时间是:"+format);
         //模拟任务执行耗时5s
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"  任务结束!现在时间是:"+sdf.format(new Date()));
    }
}

3.创建CronTrigger与scheduler

在定义过Job类之后,我们就可以通过创建CronTrigger与scheduler来执行定时任务了,为了简单,代码都写在了main方法里

package com.example.demo.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.spi.MutableTrigger;

import java.util.Date;

public class QuartzTest {

    public static void main(String[] args) throws SchedulerException {
        //1.创建一个jobDetail,并与PrintTimeJob绑定,Job任务的name为printJob
        JobDetail jobDetail = JobBuilder.newJob(PrintTimeJob.class).withIdentity("printJob").build();

        //2.使用cron表达式,构建CronScheduleBuilder
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
        //使用TriggerBuilder构建cronTrigger,并指定了Trigger的name和group
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("job1", "group1")
                .withSchedule(cronScheduleBuilder).build();
        //3.创建scheduler
        StdSchedulerFactory factory = new StdSchedulerFactory();
        Scheduler scheduler = factory.getScheduler();
        //4.将job和cronTrigger加入scheduler进行管理
        scheduler.scheduleJob(jobDetail,cronTrigger);
        //5.开启调度
        scheduler.start();
    }
}

执行main方法,可以看到控制台输出,如下图:
在这里插入图片描述

四、如何动态修改定时任务间隔

通过上面的代码我们知道了如何通过Quartz实现定时任务,那么关键的问题来了——如何动态的修改任务间隔?这个问题其实可以分解为三小个问题:
在这里插入图片描述

  • 如何将scheduler变为spring容器管理?
    在SpringBoot中很容易实现,使用 @Bean或者 @Autowired注入Scheduler 即可。

  • 如何重新设置Job任务的Trigger?
    既然scheduler是任务的调度器,那么我们自然想到scheduler中是否有相关API,果然发现了scheduler中的rescheduleJob(TriggerKey key, Trigger trigger)方法,从方法名很明显看出,这是在重新设置任务的触发器,我们修改任务的时间间隔,就是在重新设置的触发器。而TriggerKey 是目标任务的标识。包含任务名字和分组名,这个在上面demo中我们设置过。

  • 如何在项目启动后,就开始任务调度?
    这个问题等同于 “如何监听springboot应用启动成功事件?”,翻阅资料发现,自Spring框架3.0版本后,自带了ApplicationListener接口,允许我们通过实现此接口监听spring框架中的的ApplicationEvent,ApplicationListener接口的源码如下:
    AppListener

ApplicationListener使用了观察者模式,实现该接口的类,会作为观察者,当特定的ApplicationEvent被触发时,spring框架反射动调用onApplicationEvent方法,更多的说明详见官网说明ApplicationListener

ApplicationEvent就是要监听的事件,查看源码发现其有很多实现类,而其中的SpringApplicationEvent下的ApplicationReadyEvent就是我们想要的监听的事件。
关于ApplicationEvent 更多说明见官网链接ApplicationEvent
在这里插入图片描述
到了这里,三个问题都解决了,思路清晰了,编起代码来,就很快了。

五、动态修改定时任务间隔

1.创建QuartzUtil工具类
package com.example.demo.util;

import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.text.SimpleDateFormat;
import java.util.Date;

@Slf4j
@Component
public class QuartzUtil {

    /**
     * 注入Scheduler
     */
    @Autowired
    private Scheduler scheduler;

    /**
     * 创建CronTrigger
     * @param triggerName  触发器名
     * @param triggerGroupName 触发器组名
     * @param cronExpression  定时任务表达式
     */
    public CronTrigger createCronTrigger(String triggerName,String triggerGroupName ,String cronExpression){
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerName, triggerGroupName)
                .withSchedule(cronScheduleBuilder).build();
        return cronTrigger;
    }

    /**
     * 创建JobDetail
     * @param jobDetailName 任务名称
     * @param jobClass  任务类,实现Job类接口
     */
    public JobDetail createCronScheduleJob(String jobDetailName,Class<? extends Job> jobClass){
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobDetailName).build();
        return jobDetail;
    }


    /**
     * 修改cron任务的执行间隔
     * @param triggerName   旧触发器名
     * @param triggerGroupName 旧触发器组名
     * @param newCronTime
     * @throws SchedulerException
     */
    public boolean updateCronScheduleJob(String triggerName,String triggerGroupName,String newCronTime) throws SchedulerException {
        Date date;
        log.info("updateCronScheduleJob 入参name={},triggerGroupName={},newCronTime={}",triggerName,triggerGroupName,newCronTime);
        TriggerKey triggerKey = new TriggerKey(triggerName, triggerGroupName);
        CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        if(ObjectUtils.isEmpty(cronTrigger)){
            log.error("获取到的cronTrigger为null!");
            return false;
        }
        String oldTime = cronTrigger.getCronExpression();
        log.info("oldTimeCron={},newCronTime={}",oldTime,newCronTime);
        if (!oldTime.equalsIgnoreCase(newCronTime)) {
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(newCronTime);
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerName, triggerGroupName)
                    .withSchedule(cronScheduleBuilder).build();
            date = scheduler.rescheduleJob(triggerKey, trigger);
            log.info("修改执行成功,下次任务开始time={}",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));
            return true;
        }else{
            log.error("oldTimeCron与newCronTime相等,修改结束");
            return false;
        }
    }

}

2.使用ApplicationListener监听
package com.example.demo.config;


import cn.hutool.core.date.DateUtil;
import com.example.demo.job.PrintTimeJob;
import com.example.demo.util.QuartzUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobDetail;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Properties;


/**
 * 监听器,启动定时任务
 *
 */
@Slf4j
@Component
public class QuartzConfig implements  ApplicationListener<ApplicationReadyEvent> {

    /**
     * 注入QuartzUtil
     */
    @Autowired
    private QuartzUtil quartzUtil;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        try {
            log.info("监听程序启动完成");
            String jobName = "printTimeJob";
            String cronTriggerName = "printTimeCronTrigger";
            String cronTriggerGroupName = "printTimeCronTriggerGroup";
             //创建定时任务PrintTimeJob,每10秒描述执行一次
            Date cronScheduleJob = quartzUtil.createCronScheduleJob(jobName, PrintTimeJob.class, cronTriggerName, cronTriggerGroupName, "0/10 * * * * ?");
            log.info("定时任务jobName={},cronTriggerName={},cronTriggerGroupName={},date={}",jobName, cronTriggerName,cronTriggerGroupName,DateUtil.format(cronScheduleJob,"yyyy-MM-dd HH:mm:ss"));
            quartzUtil.scheduleJob();
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.error("监听程序启动失败");
        }
    }
}

3.创建Controller,模拟动态修改定时任务间隔
package com.example.demo.controller;


import com.example.demo.config.CompanyWeChatConfig;
import com.example.demo.util.ApiRes;
import com.example.demo.util.QuartzUtil;
import com.example.demo.util.ResultEnum;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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 java.util.Map;


/**
* @Author:         wgq
* @CreateDate:     2019-11-8 13:43:21
* @Description:    quartz测试
* @Version:        1.0
*/
@Slf4j
@RestController
@RequestMapping(value = "/qz")
public class QuartzController {

    
    /*
    *注入QuartzUtil 
    **/
    @Autowired
    QuartzUtil quartzUtil;

     /**
     * 修改定时任务间隔
     * @param triggerName 触发器名称
     * @param groupName  触发器组名
     * @param newCronTime
     * @return
     */
    @PostMapping(value = "updateCronScheduleJob",produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "修改cron任务执行间隔")
    public boolean sendMsg(@RequestParam String triggerName, @RequestParam String groupName,@RequestParam String newCronTime){
        try {
            return  quartzUtil.updateCronScheduleJob(triggerName, groupName, newCronTime);
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }


}

4.模拟动态修改定时任务间隔

1.现在我们启动项目,会发现在项目启动完成后,我们的自定义监听类QuartzConfig里面的onApplicationEvent方法被触发,我们的PrintTimeJob开始按照每10秒一次的频率执行。
在这里插入图片描述2.现在我们使用postman调用QuartzControllerupdateCronScheduleJob方法,将定时任务修改为每30秒一次
在这里插入图片描述发送请求后,查看控制台打印信息,修改成功,定时任务变为每30秒执行一次!
在这里插入图片描述
OK,到了这里大功告成了!
当然Quartz功能不止如此,我们还可以动态的创建、停止定时任务等等,留给大家去探索。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值