其他分享
首页 > 其他分享> > 【Quartz】初识与基本使用

【Quartz】初识与基本使用

作者:互联网

个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道

如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充


前言

本文只做基于SpringBoot的示例,其余版本的请自行查阅资料,大同小异


1.介绍

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:

  • 持久性作业 - 就是保持调度定时的状态;
  • 作业管理 - 对调度作业进行有效的管理;

使用quartz的调度器统一管理定时任务,可以动态的添加、删除、暂停等

推荐几个博客,写的真的挺好

2.使用步骤

2.1.4静态定时器

静态定时器即主要使用代码控制,一旦项目部署将无法对定时器做出调整(增删改),适用于强制执行而不需要灵活变通的任务

  1. 添加quartz依赖

    <!-- 定时器依赖 -->
    <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.2</version>
    </dependency>
    
  2. 创建需要执行的任务类MyTask,必须继承并实现QuartzJobBean

    public class MyTask extends QuartzJobBean {
        /**
         * 计数器
         */
        private static Integer count = 0;
    
        /**
         * 执行内容
         *
         * @param jobExecutionContext 上下文
         */
        @Override
        protected void executeInternal(JobExecutionContext jobExecutionContext) {
            JobDetail jobDetail = jobExecutionContext.getJobDetail();
            Trigger trigger = jobExecutionContext.getTrigger();
            synchronized (count) {
                System.out.println(""
                        + new Date()    //打印时间
                        + " :" + count  //打印计时器
                        + "  jobDetailKey : " + jobDetail.getKey().toString()   //打印jobDetail的key
                        + "  jobDetailData : " + jobDetail.getJobDataMap().getString("jobName") //打印来自jobDetail的参数
                        + "  triggerKey : " + trigger.getKey().toString()   //打印trigger的key
                        + "  triggerData : " + trigger.getJobDataMap().getString("triggerName") //打印来自trigger的参数
                );
                count++;
            }
        }
    }
    
  3. 创建quartz配置文件QuartzConfig.java

    1. 定义业务组件myJob1(),配置需要执行的任务类、业务自定义身份、转递给任务的参数等等
    2. 定义触发器myTrigger1(),配置关联的业务组件、触发器自定义身份、转递给任务的参数等等
    3. 别忘了使用@Configuration标记配置文件类、@Bean标记业务组件和触发器
    @Configuration
    public class QuartzConfig {
        @Bean
        public JobDetail myJob1() {
            JobDetail jobDetail = JobBuilder.newJob(MyTask.class)
                    .withIdentity("myJob1", "myJobGroup1")
                    //JobDataMap可以给任务execute传递参数,且可传递多个
                    .usingJobData("jobName", "myJob1")
                    .storeDurably()
                    .build();
            return jobDetail;
        }
    
        @Bean
        public Trigger myTrigger1() {
            Trigger trigger = TriggerBuilder.newTrigger()
                    .forJob(myJob1())
                    .withIdentity("myTrigger1", "myTriggerGroup1")
                    //JobDataMap可以给任务execute传递参数,且可传递多个
                    .usingJobData("triggerName", "myTrigger1")
                    .startNow()
                    //.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
                    .withSchedule(CronScheduleBuilder.cronSchedule("*/3 * * * * ?"))
                    .build();
            return trigger;
        }
    
        @Bean
        public Trigger myTrigger2() {
            Trigger trigger = TriggerBuilder.newTrigger()
                    .forJob(myJob1())
                    .withIdentity("myTrigger2", "myTriggerGroup1")
                    .usingJobData("triggerName", "myTrigger2")
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
    //                .withSchedule(CronScheduleBuilder.cronSchedule("*/3 * * * * ?"))
                    .build();
            return trigger;
        }
    }
    

执行结果如下

image-20200908094332929


2.2.动态定时器

动态定时器即可以对定时器动态进行调整,通常由前端页面和数据库配合,从而灵活调整定时器

因为需要动态管理,那么数据必须保证持久化,否则重新启动项目就什么都不剩了,那肯定不得行

添加quartz依赖

        <!-- 定时器依赖 -->
        <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

持久化配置文件

写入配置文件即可,仅编写quartz相关部分,其余按照自己需求即可,不影响

推荐下面两篇博文,介绍的很详细,此处只做简单实现

spring:
  quartz:
    #相关属性配置
    properties:
      org:
        quartz:
          scheduler:
            instanceName: quartzScheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_
            isClustered: false
            clusterCheckinInterval: 10000
            useProperties: false
            misfireThreshold : 5000
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
    #数据库方式
    job-store-type: JDBC
    #初始化表结构
    jdbc:
      initialize-schema: NEVER

建表

表需要依照quartz官方给出的实例,可以到http://www.quartz-scheduler.org/downloads/下载后解压,将目录\src\org\quartz\impl\jdbcjobstore\tables_mysql_innodb.sql里的语句在数据库直接执行即可

嫌卡或者懒得可以在这个地址获取2.3.0版本https://gitee.com/echo_ye/assets/blob/master/doc/tables_mysql_innodb.sql

数据封装类和查询方法

数据封装类需要包括前后端传递的所有参数,查询则需要向quartz相关的表中查询

<?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.yezi_tool.demo_basic.mapper.JobMapper">
    <select id="listJob" resultType="com.yezi_tool.demo_basic.entity.QuartzJob">
        SELECT
          job.JOB_NAME as jobName,
          job.JOB_GROUP as jobGroup,
          job.DESCRIPTION as description,
          job.JOB_CLASS_NAME as jobClassName,
          cron.CRON_EXPRESSION as cronExpression,
          tri.TRIGGER_NAME as triggerName,
          tri.TRIGGER_STATE as triggerState,
          job.JOB_NAME as oldJobName,
          job.JOB_GROUP as oldJobGroup
        FROM qrtz_job_details AS job
        LEFT JOIN qrtz_triggers AS tri ON job.JOB_NAME = tri.JOB_NAME
        LEFT JOIN qrtz_cron_triggers AS cron ON cron.TRIGGER_NAME = tri.TRIGGER_NAME
        WHERE tri.TRIGGER_TYPE = 'CRON'
    </select>
</mapper>
package com.yezi_tool.demo_basic.mapper;

import com.yezi_tool.demo_basic.entity.QuartzJob;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface JobMapper {
    List<QuartzJob> listJob();
}

package com.yezi_tool.demo_basic.entity;

import lombok.Data;

import java.util.List;
import java.util.Map;

@Data
public class QuartzJob {
    private String jobName;//任务名称
    private String jobGroup;//任务分组
    private String description;//任务描述
    private String jobClassName;//执行类
    private String cronExpression;//执行时间

    private String triggerState;//任务状态

    private List<Map<String,Object>> dataMap;//参数
    
    private List<Map<String, Object>> jobDataParam;//备用数据域


    public QuartzJob() {
        super();
    }

    public QuartzJob(String jobName, String jobGroup, String description, String jobClassName, String cronExpression) {
        super();
        this.jobName = jobName;
        this.jobGroup = jobGroup;
        this.description = description;
        this.jobClassName = jobClassName;
        this.cronExpression = cronExpression;
    }
}

执行的任务类

与静态的任务类相同,可以建立多个,但是最好保证在同一个目录下,方便实例化的时候确认路径

本案例的任务类放置在目录com.yezi_tool.demo_basic.test

服务层方法

控制层为主要代码,在这里对定时任务进行增删改查等操作

因为懒就不写接口实现了。。。实际使用中请自己补上

重点为创建定时任务的方法。步骤如下

  1. 检查任务是否已存在,若是,则删除任务
  2. 根据任务类的名字(路径固定),实例化任务类task
  3. 创建定时任务job,设置目标任务类、job名、分组名、传递给任务的数据、任务描述等参数
  4. 创建触发器trigger,设置触发器的名字、分组、触发时间、传递给任务的数据等参数
  5. 通过调度程序schedule将job和trigger进行绑定并启动

也可在trigger中绑定job,那么schedule直接启动trigger即可

@Service
public class QuartzService {

    public String QUARTZ_TASK_PATH_HEAD = "com.yezi_tool.demo_basic.test" + ".";

    private final Scheduler scheduler;

    private final JobMapper jobMapper;

    public QuartzService(Scheduler scheduler, JobMapper jobMapper) {
        this.scheduler = scheduler;
        this.jobMapper = jobMapper;
    }

    public List<QuartzJob> list(String jobName) {
        List<QuartzJob> list = jobMapper.listJob();
        list.forEach(m -> {
            //只查询类名,无视路径
            m.setJobClassName(m.getJobClassName().replace(QUARTZ_TASK_PATH_HEAD, ""));
        });
        return list;
    }

    /**
     * 保存定时任务
     *
     * @param quartzJob 定时任务类
     * @throws Exception 抛出异常
     */
    public void save(QuartzJob quartzJob) throws Exception {
        try {
            //组装参数
            JobKey jobKey = new JobKey(quartzJob.getJobName(), quartzJob.getJobGroup());
            JobDataMap jobDataMap = new JobDataMap();
            if (quartzJob.getDataMap() != null) {
                for (Map<String, Object> map : quartzJob.getDataMap())
                    jobDataMap.putAll(map);
            }

            //删除旧的job
            if (scheduler.checkExists(jobKey)) {
                scheduler.deleteJob(jobKey);
            }

            //构建新的job
            Class clazz = Class.forName(QUARTZ_TASK_PATH_HEAD + quartzJob.getJobClassName());   //实例化目标任务类
            clazz.getDeclaredConstructor().newInstance();
            JobDetail jobDetail = JobBuilder
                    .newJob(clazz)//设置目标任务类
                    .withIdentity(quartzJob.getJobName(), quartzJob.getJobGroup())//设置job名和分组名
                    .usingJobData("jobName", quartzJob.getJobName())//传递的参数,可以自定义,可传递多组,以map形式传递
//                    .usingJobData(new JobDataMap(quartzJob.getDataMap()))
                    .withDescription(quartzJob.getDescription())//设置描述,可为空
                    .build();

            //构建新的触发器
            Trigger trigger = TriggerBuilder
                    .newTrigger()
//                    .forJob(jobDetail)//目标job,这里不设置,如果设置了将会自动触发
                    .withIdentity(quartzJob.getJobName() + "Trigger", quartzJob.getJobGroup())//设置job名和分组名
                    .usingJobData("triggerName", quartzJob.getJobName() + "Trigger")//传递的参数,可以自定义,可传递多组,以map形式传递
                    .usingJobData(jobDataMap)
                    .withSchedule(CronScheduleBuilder
                            .cronSchedule(quartzJob.getCronExpression().trim())
                            .withMisfireHandlingInstructionDoNothing()
                    )
                    .startNow()//现在就启动
//                    .startAt(new Date())//启动时间,可以自定义,与startNow冲突
                    .build();

            //通过schedule触发
            scheduler.scheduleJob(jobDetail, trigger);

        } catch (SchedulerException e) {
            throw new BaseException("保存定时任务失败");
        } catch (ClassNotFoundException e) {
            throw new BaseException("任务目标类不存在");
        }
    }


    /**
     * 启动任务
     *
     * @param jobName  任务名
     * @param jobGroup 任务分组
     * @param dataMap  数据,可为空
     * @throws Exception 相关异常
     */
    public void trigger(String jobName, String jobGroup, List<Map<String, Object>> dataMap) throws Exception {
        //组装参数
        JobKey jobKey = new JobKey(jobName, jobGroup);
        JobDataMap jobDataMap = new JobDataMap();
        if (dataMap != null) {
            for (Map<String, Object> map : dataMap)
                jobDataMap.putAll(map);
        }
        //启动触发器
        try {
            scheduler.triggerJob(jobKey, jobDataMap);
        } catch (SchedulerException e) {
            throw new BaseException("启动定时任务失败");
        }
    }

    /**
     * 暂停任务
     *
     * @param jobName  任务名
     * @param jobGroup 任务分组
     * @throws Exception 相关异常
     */
    public void pause(String jobName, String jobGroup) throws Exception {
        //组装参数
//        JobKey jobKey = new JobKey(jobName, jobGroup);
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", jobGroup);

        //暂停触发器
        try {
//            scheduler.pauseJob(jobKey);
            scheduler.pauseTrigger(triggerKey);
        } catch (SchedulerException e) {
            throw new BaseException("暂停定时任务失败");
        }
    }

    /**
     * 继续任务
     *
     * @param jobName  任务名
     * @param jobGroup 任务分组
     * @throws Exception 相关异常
     */
    public void resume(String jobName, String jobGroup) throws Exception {
        //组装参数
//        JobKey jobKey = new JobKey(jobName, jobGroup);
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", jobGroup);

        //继续触发器
        try {
//            scheduler.resumeJob(jobKey);
            scheduler.resumeTrigger(triggerKey);
        } catch (SchedulerException e) {
            throw new BaseException("启动定时任务失败");
        }
    }

    /**
     * 取消任务
     *
     * @param jobName  任务名
     * @param jobGroup 任务分组
     * @throws Exception 相关异常
     */
    public void cancel(String jobName, String jobGroup) throws Exception {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", jobGroup);
            JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
            //暂停
            scheduler.pauseTrigger(triggerKey);
            //解除绑定
            scheduler.unscheduleJob(triggerKey);
            //删除触发器
            scheduler.deleteJob(jobKey);
        } catch (SchedulerException e) {
            throw new BaseException("取消定时任务失败");
        }
    }

    /**
     * 取消全部任务
     *
     * @throws Exception 相关异常
     */
    public void cancelAll() throws Exception {
        List<QuartzJob> jobList = jobMapper.listJob();
        for (QuartzJob quartzJob : jobList) {
            cancel(quartzJob.getJobName(), quartzJob.getJobGroup());
        }
    }

    /**
     * 暂停全部任务
     *
     * @throws Exception 相关异常
     */
    public void pauseAll() throws Exception {
        try {
            scheduler.pauseAll();
        } catch (SchedulerException e) {
            throw new BaseException("暂停定时任务失败");
        }
    }

    /**
     * 恢复所有任务
     *
     * @throws Exception
     */
    public void resumeAll() throws Exception {
        try {
            scheduler.resumeAll();
        } catch (SchedulerException e) {
            throw new BaseException("启动定时任务失败");
        }
    }

}

控制层

直接调用服务层方法就可以了,没啥技术含量

这里只调用部分功能用于测试,请按照实际需求调整

@Controller
@RequestMapping("/quartz")
@Slf4j
public class QuartzController {
    private QuartzService quartzService;

    public QuartzController(QuartzService quartzService) {
        this.quartzService = quartzService;
    }

    @GetMapping("/list")
    @ResponseBody
    public ReturnMsg list(String jobName) {
        log.info("查询列表");
        return ReturnMsg.success(quartzService.list(jobName));
    }

    @PostMapping("/save")
    @ResponseBody
    public ReturnMsg save(@RequestBody QuartzJob quartzJob) throws Exception {
        log.info("保存");
        quartzService.save(quartzJob);
        return ReturnMsg.success();
    }

    @PostMapping("/pauseAll")
    @ResponseBody
    public ReturnMsg pauseAll() throws Exception {
        log.info("全部暂停");
        quartzService.pauseAll();
        return ReturnMsg.success();
    }

    @PostMapping("/resumeAll")
    @ResponseBody
    public ReturnMsg resumeAll() throws Exception {
        log.info("恢复全部");
        quartzService.resumeAll();
        return ReturnMsg.success();
    }

    @PostMapping("/cancelAll")
    @ResponseBody
    public ReturnMsg cancelAll() throws Exception {
        log.info("删除全部");
        quartzService.cancelAll();
        return ReturnMsg.success();
    }
}


测试结果

save的参数如下,其余请求为空参数

{
"jobName": "job1",
"jobGroup": "group1",
"description": "demoJob",
"jobClassName": "MyTask",
"cronExpression": "*/3 * * * * ?"
}

3.补充

4.可能遇到的坑


5.demo地址

https://gitee.com/echo_ye/demo_basic/tree/scheduleDemo

仅实现了部分功能作为样例,请按照需求自己扩展哦,有疑问或者建议欢迎联系我~

BB两句

quartz之前只在spring用过。。。本来以为就一个小组件,打算几个定时器整理在一起得了。。。结果越学越多。。不得不分离出来作为单独的文章

当然整理出来的依然只有一小部分,后面有机会再扩充整理一遍

而且讲道理quartz的注释是写的真的挺好,我这个英语渣渣跟着debug看源码也问题不大



作者:Echo_Ye

WX:Echo_YeZ

EMAIL :echo_yezi@qq.com

个人站点:在搭了在搭了。。。(右键 - 新建文件夹)

标签:基本,Quartz,quartzJob,job,初识,jobName,任务,trigger,public
来源: https://www.cnblogs.com/silent-bug/p/13647343.html