springboot2集成quartz详细步骤,如何通过接口管理quartz定时任务、如何通过配置实现一个固定的quartz定时任务、如何配置quartz、以及如何自动初始化quartz的数据库。还有讲解如何使用springboot自带的定时器实现定时任务的方式。本文不讲解原理,只讲解如何使用。

1.使用springboot自带的schedule实现定时任务

        不用引用任何第三方的工具包

a. 首先在启动类上增加@EnableScheduling注解,开启定时任务的支持

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class SpringTestApplication {

    public static void main(String[] args){

        SpringApplication.run(SpringTestApplication.class, args);
    }
}

b. 然后定义自己的定时任务业务逻辑类

        加上注解@Component或@Configuration,在定时任务的具体逻辑方法加上注解@Schedule("${cron表达式--如:0/10 * * * * ?}")

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class SpringScheduleTest {

    /**
     * 添加定时任务(使用corn表达式设置定时任务执行时间,例如 10秒:0/10 * * * * ?)
     * 可直接指定时间间隔,例如:5秒 @Scheduled(fixedRate=5000)
     * @author liuyong
     * @date 2022/5/24
     */
    @Scheduled(cron = "0/10 * * * * ?")
    private void cleanSessionInfo() {
        System.err.println("Springboot Schedule定时任务测试");
    }

}

这样一定固定的定时任务就完成了,在方法中实现自己的定时任务逻辑即可

2. 集成使用quartz实现定时任务

a. 首先增加pom.xml配置,引入quartz依赖:

        <!-- quartz定时任务 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>

b. 然后在yml文件配置quartz的基础配置:

quartz在spring配置下 spring.quartz

# quartz定时任务配置
  quartz:
    # 数据库存储方式
    job-store-type: jdbc
    # 关闭系统的时候,等待定时任务完成
    wait-for-jobs-to-complete-on-shutdown: true
    # 初始化后是否自动启动计划程序
    auto-startup: true
    # 延迟启动
    startup-delay: 5s
    jdbc:
      # SQL 初始化脚本中单行注释的前缀
      comment-prefix: --
      # 数据库架构初始化模式--never 从不进行初始化,always 每次都清空数据库进行初始化,embedded 只初始化内存数据库(默认值)
      initialize-schema: never
      # 用于初始化数据库架构的SQL文件的路径
      schema: classpath:database/mysql.sql
    properties:
      org:
        quartz:
#          dataSource:
#            quartzDS:
#              driver: com.mysql.jdbc.Driver
#              url: jdbc:mysql://localhost:3307/quartz?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
#              username: quartz
#              password: quartz
          scheduler:
            #调度标识名 集群中每一个实例都必须使用相同的名称;ID设置为自动获取 每一个必须不同
            instanceName: TestScheduler
            # 集群配置节点实例ID
            instanceId: AUTO
            rmi:
              # 如果希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true
              # 在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'
              # 指定一个'true'值是没有意义的,如果这样做'export'选项将被忽略
              export: false
              #如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。
              #还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099
              proxy: false
          # quartz线程池配置
          threadPool:
            #实例化ThreadPool时,使用的线程类为SimpleThreadPool
            class: org.quartz.simpl.SimpleThreadPool
            #threadCount和threadPriority将以setter的形式注入ThreadPool实例
            #并发个数:如果只有几个工作每天触发几次 那么1个线程就可以,如果有成千上万的工作,每分钟都有很多工作 那么就需要50-100之间.
            #只有1到100之间的数字是非常实用的
            threadCount: 10
            #优先级 默认值为5
            threadPriority: 5
            #可以是“true”或“false”,默认为false
            threadsInheritContextClassLoaderOfInitializingThread: true
          # quartz的任务存储方式
          jobStore:
            # 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失;
            #此处配置通过事务存储数据库;2.5.6版本之后,配置JobStoreTX要报错,调整配置
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
#            class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
            #您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作
            # StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            #表前缀
            tablePrefix: qrtz_
#            #数据源别名,自定义
#            dataSource: quartzDS
            #是否加入集群(集群时,一个任务只有一个服务实例执行)
            #设置为“true”以打开群集功能。如果您有多个Quartz实例使用同一组数据库表,则此属性必须设置为“true”,否则您将遇到破坏
            isClustered: flase
            #调度实例失效的检查时间间隔
            clusterCheckinInterval: 10000
            #可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,
            #因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题
            useProperties: false

c. 自动初始化数据库:

数据库连接,使用正常的springboot项目的jdbc+alibaba的druid即可

首先到地址找到自己使用的数据库的sql文件,并放到自己项目的resource目录下:quartzjdbc/src/main/resources/sql at master · wangmaoxiong/quartzjdbc · GitHubquartz 定时器 jdbc 持久化调度信息. Contribute to wangmaoxiong/quartzjdbc development by creating an account on GitHub.https://github.com/wangmaoxiong/quartzjdbc/tree/master/src/main/resources/sql

然后调整yml中 配置jdbc: initialize-schema: never 与 jdbc: schema: classpath:database/mysql.sql。never该改为always,schema改为自己下载的sql文件存储位置

注意:第一次启动后,需要将always改为never,否则后续每次启动都会重新初始化quartz数据库

d. 定义定时任务业务逻辑类

        其中将DisallowConcurrentExecution注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例,即同一个job,即使时间到了,只要上一个还在运行,新的就等待

@DisallowConcurrentExecution
public class QuartzTestTask implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info(LocalDateTime.now() + "=======开始我的测试quartz定时任务!");
        try {
            log.info(LocalDateTime.now() + "=======执行我的测试quartz定时任务中......");
            Thread.sleep(3000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info(LocalDateTime.now() + "=======我的测试quartz定时任务结束!");

    }
}

e. 注册定时任务:

        此处有两种方式:

        ①. 配置固定定时任务

import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FixedJobConfig {

    // 使用jobDetail包装job
    @Bean
    public JobDetail myJobDetail() {
        return JobBuilder.newJob(QuartzTestTask.class)
                .withIdentity("myFixedJob") // 设置job任务的名称
//                .usingJobData("userName", "zhangsan")//参数
                .storeDurably()
                .build();
    }

    // 把jobDetail注册到trigger上去
    @Bean
    public Trigger myJobTrigger() {
        // 每5秒一次
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");

        return TriggerBuilder.newTrigger()
                .forJob(myJobDetail()) //关联jobDetail
                .withIdentity("myFixedJobTrigger") // 设置触发器的名称
                .withSchedule(cronScheduleBuilder)
                .build();
    }
}

这样配置后,定时任务即可生效。

        ②. 通过接口管理定时任务:

        先定义一个quartz定时任务的管理工具类

import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

import java.util.*;

/**
  * quartz管理工具类
  * @author kevin
  * @date 2022/5/24
  */
@Slf4j
@Component
public class QuartzManager {
    //调度工厂
    @Autowired
    private SchedulerFactoryBean schedulerFactory;

    public Scheduler getScheduled() throws SchedulerException {

        return schedulerFactory.getScheduler();
    }

    /**
     * 添加定时任务
     * @param jobGroupName job任务分组名称
     * @param triggerGroupName job触发器分组名称
     * @param jobName 任务名称
     * @param jobClazz 任务job
     * @param cron 执行时间之cron表达式
     * @param params 任务job参数
     */
    public void addJob(String jobGroupName, String triggerGroupName, String jobName , Class jobClazz ,
                       String cron, Map<String,String> params) throws SchedulerException {
        //获取Scheduler
        Scheduler scheduler = getScheduled();
        // 任务名,任务组,任务执行类
        JobDetail jobDetail= JobBuilder.newJob(jobClazz)
                .withIdentity(jobName, jobGroupName)
                .storeDurably() //配置存储到数据库
                .build();
        // 传参(可选)
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        if(params != null ){
            params.forEach(jobDataMap::put);
        }

        //触发器时间设置
        CronScheduleBuilder cronBuilder = CronScheduleBuilder.cronSchedule(cron);
        // 触发器创建,创建Trigger对象
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(jobName, triggerGroupName)// 触发器名(设置为任务名),触发器组名称
                .withSchedule(cronBuilder) // 触发器时间设定
                .startNow().build();

        // 调度容器设置JobDetail和Trigger
        scheduler.scheduleJob(jobDetail, trigger);
        // 启动定时任务
        if (!scheduler.isShutdown()) {
            scheduler.start();
        }
    }

    /**
      * 更新定时任务的cron表达式--根据定时任务名称
      * @author kevin
      * @param triggerGroupName 需要修改的定时任务的触发器分组名称
      * @param jobName 需要修改的定时任务的名称
      * @param cron    新的cron表达式
      * @date 2022/5/24 17:13
      */
    public void updateJob(String triggerGroupName, String jobName, String cron) throws SchedulerException{
        //获取Scheduler
        Scheduler scheduler = getScheduled();

        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, triggerGroupName);
        // 触发器时间定义
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        // 创建Trigger对象
        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .withIdentity(triggerKey)
                .withSchedule(scheduleBuilder)
                .build();
        scheduler.rescheduleJob(triggerKey, cronTrigger);
    }

    /**
     * 删除定时任务--根据定时任务名称
     * @param jobGroupName job任务分组名称
     * @param triggerGroupName job触发器分组名称
     * @param jobName 任务名称
     */
    public void removeJob(String jobGroupName, String triggerGroupName, String jobName)  throws SchedulerException{
        Scheduler scheduler = getScheduled();
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, triggerGroupName);
        scheduler.pauseTrigger(triggerKey);// 停止触发器
        scheduler.unscheduleJob(triggerKey);// 移除触发器
        scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 删除任务
    }

    /**
      * 获取所有定时任务
      * @author kevin
      * @return java.util.List<java.lang.String>
      * @date 2022/5/24 17:06
      */
    public List<String> getAllJob() throws SchedulerException{
        List<String> jobs = new ArrayList<>();
        Set<JobKey> jobKeys = getScheduled().getJobKeys(GroupMatcher.anyGroup());
        for(JobKey jobKey:jobKeys){
            jobs.add(jobKey.getName());
        }
        return jobs;
    }

    /**
      * 停止所有的定时任务
      * @author kevin
      * @date 2022/5/24 17:05
      */
    public void shutdownJobs() throws SchedulerException{
        Scheduler scheduler = getScheduled();
        if (!scheduler.isShutdown()) {
            scheduler.shutdown();
        }
    }

    /**
      * 启动所有的定时任务
      * @author kevin
      * @date 2022/5/24 17:05
      */
    public void startJobs() throws SchedulerException{
        Scheduler scheduler = getScheduled();
        scheduler.start();
    }
}

然后编写接口controller:

import com.liu.utils.QuartzManager;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequestMapping("/job")
public class JobManageController {

    @Autowired
    private QuartzManager quartzManager;

    /**
      * 删除job
      * @author kevin
      * @return java.util.Map<java.lang.String,java.lang.Object>
      * @date 2022/5/24 17:57
      */
    @GetMapping("/getJobs")
    public Map<String, Object> getJobs() {
        Map<String, Object> result = new HashMap<>();
        try {
            result.put("code", 200);
            result.put("msg", "定时任务列表查询成功!");
            result.put("data", quartzManager.getAllJob());
            return result;
        } catch (SchedulerException e) {
            e.printStackTrace();
            result.put("code", 201);
            result.put("msg", "定时任务列表查询失败!");
            result.put("data", null);
            return result;
        }

    }

    /**
     * 新增job任务
     *
     * @param jobName          job名称
     * @param jobGroupName     job分组名称
     * @param triggerGroupName 触发器分组名称
     * @param className        需要执行的job的类名
     * @param cron             cron 表达式
     * @return java.util.Map<java.lang.String,java.lang.Object>
     */
    @PostMapping("/addJob")
    public Map<String, Object> addJob(String jobName, String jobGroupName, String triggerGroupName,
                             String className, String cron, Map<String, String> params) {
        Class<? extends Job> jobClass;
        try{
            jobClass = (Class<? extends Job>) Class.forName("com.liu.task." + className);
            quartzManager.addJob(jobGroupName, triggerGroupName, jobName, jobClass, cron, params);
        }catch (ClassNotFoundException e){
            Map<String, Object> result = new HashMap<>();
            result.put("code", 201);
            result.put("msg", "定时任务不存在!");
            result.put("data", null);
            return result;
        } catch (SchedulerException e) {
            Map<String, Object> result = new HashMap<>();
            result.put("code", 201);
            result.put("msg", "定时任务创建失败!");
            result.put("data", null);
            return result;
        }

        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "定时任务创建成功!");
        result.put("data", null);
        return result;
    }

    /**
     * 修改定时任务
     * @param triggerGroupName 需要修改的定时任务的触发器分组名称
     * @param jobName 需要修改的定时任务的名称
     * @param cron    新的cron表达式
     * @return java.util.Map<java.lang.String,java.lang.Object>
     */
    @PutMapping("/updateJob")
    public Map<String, Object> updateJob(String triggerGroupName, String jobName, String cron) {
        try {
            quartzManager.updateJob(triggerGroupName, jobName, cron);
        } catch (SchedulerException e) {
            e.printStackTrace();
            Map<String, Object> result = new HashMap<>();
            result.put("code", 201);
            result.put("msg", "定时任务更新失败!");
            result.put("data", null);
            return result;
        }
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "定时任务更新成功!");
        result.put("data", null);
        return result;
    }

    /**
     * 删除job
     * @param jobGroupName     定时任务分组名称
     * @param triggerGroupName 触发器分组名称
     * @param jobName          定时任务名称
     * @return java.util.Map<java.lang.String,java.lang.Object>
     */
    @DeleteMapping("/deleteJob")
    public Map<String, Object> deleteJob(String jobGroupName, String triggerGroupName, String jobName) {
        try {
            quartzManager.removeJob(jobGroupName, triggerGroupName, jobName);
        } catch (SchedulerException e) {
            e.printStackTrace();
            Map<String, Object> result = new HashMap<>();
            result.put("code", 201);
            result.put("msg", "定时任务更新失败!");
            result.put("data", null);
            return result;
        }

        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "定时任务删除成功!");
        result.put("data", null);
        return result;
    }
}

如此即可通过接口对定时任务进行管理(具体的定时任务逻辑实现类还是需要后台自己编码实现,接口管理只是对已有实现逻辑的定时任务进行配置与管理,其中addJob中className就是自己编码实现的定时任务逻辑实现类的类名)

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐