精华内容
下载资源
问答
  • 2020-07-21 15:55:47

    定时任务在系统中用到的地方很多,例如每晚凌晨的数据备份,每小时获取第三方平台的 Token 信息等等,之前我们都是在项目中规定这个定时任务什么时候启动,到时间了便会自己启动,那么我们想要停止这个定时任务的时候,就需要去改动代码,还得启停服务器,这是非常不友好的事情

    直至遇见 Quartz,利用图形界面可视化管理定时任务,使得我们对定时任务的管理更加方便,快捷

    一、Quartz 简介

    Quartz是一个开源的作业调度框架,它完全由Java写成,并设计用于J2SE和J2EE应用中。它提供了巨大的灵 活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,EJB作业预构 建,JavaMail及其它,支持cron-like表达式等等。

    二、开发前戏

    1、引入 maven 依赖

    <!-- web支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Quartz 定时任务 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    

    这里引入了 web 的依赖,以及 Quartz 的依赖,其余依赖请根据需求自行引入

    2、创建数据表

    数据模型:
    数据模型
    SQL语句:

    drop table if exists sys_quartz;
    
    /*==============================================================*/
    /* Table: sys_quartz                                            */
    /*==============================================================*/
    create table sys_quartz
    (
       id                   bigint(20) not null auto_increment comment '主键id',
       class_name           varchar(32) comment '任务类名',
       cron_expression      varchar(32) comment 'cron表达式',
       param                varchar(32) comment '参数',
       descript             varchar(11) comment '描述',
       quartz_status        varchar(255) comment '启动状态(0--启动1--停止)',
       create_time          datetime comment '创建时间',
       create_user          bigint(20) comment '创建人',
       status               tinyint(1) default 0 comment '状态(0--正常1--停用)',
       del_flag             tinyint(1) default 0 comment '删除状态(0,正常,1已删除)',
       primary key (id)
    )
    type = InnoDB;
    
    alter table sys_quartz comment '定时任务信息表';
    

    三、开发进行中

    1、创建实体类

    import com.baomidou.mybatisplus.annotation.*;
    import com.baomidou.mybatisplus.extension.activerecord.Model;
    import com.zyxx.common.annotation.Dict;
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.experimental.Accessors;
    
    import java.io.Serializable;
    
    /**
     * <p>
     * 定时任务信息表
     * </p>
     *
     * @author lizhou
     * @since 2020-07-21
     */
    @Data
    @EqualsAndHashCode(callSuper = false)
    @Accessors(chain = true)
    @TableName("sys_quartz")
    @ApiModel(value="SysQuartz对象", description="定时任务信息表")
    public class SysQuartz extends Model<SysQuartz> {
    
        @ApiModelProperty(value = "主键id")
        @TableId(value = "id", type = IdType.AUTO)
        private Long id;
    
        @ApiModelProperty(value = "任务类名")
        @TableField("class_name")
        private String className;
    
        @ApiModelProperty(value = "cron表达式")
        @TableField("cron_expression")
        private String cronExpression;
    
        @ApiModelProperty(value = "参数")
        @TableField("param")
        private String param;
    
        @ApiModelProperty(value = "描述")
        @TableField("descript")
        private String descript;
    
        @ApiModelProperty(value = "启动状态(0--启动1--停止)")
        @TableField("quartz_status")
        private Integer quartzStatus;
    
        @ApiModelProperty(value = "状态(0--正常1--停用)")
        @TableField("status")
        private Integer status;
    
        @ApiModelProperty(value = "删除状态(0--未删除1--已删除)")
        @TableField("del_flag")
        @TableLogic
        private Integer delFlag;
    
        @ApiModelProperty(value = "创建者")
        @TableField("create_user")
        private Long createUser;
    
        @ApiModelProperty(value = "创建时间")
        @TableField("create_time")
        private String createTime;
    
        @Override
        protected Serializable pkVal() {
            return this.id;
        }
    }
    

    2、实现定时任务的 CRUD

    下面我们就要完成定时任务的 新增、修改、删除、启停 等基本操作了,由于不是很复杂,这里的代码就不贴出来了,贴几张图吧

    列表页:
    列表页
    新增页:
    新增页

    四、定时任务

    1、定时任务类

    我们把定时任务都放在 job 包下面,一个定时任务就是一个文件,写一个测试的类 TestJob.java

    import com.zyxx.common.utils.DateUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.quartz.Job;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    
    /**
     * @ClassName TestJob
     * 测试定时任务
     * @Author Lizhou
     * @Date 2020-07-21 10:58:58
     **/
    @Slf4j
    public class TestJob implements Job {
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            System.out.println("定时任务启动:" + DateUtils.getYmdHms());
        }
    }
    

    TestJob 这个类实现了 Job 接口,实现了 execute 方法,这里还可以接收参数

    这个文件在 com.zyxx.sbm.job 包下面,那么在页面新增定时任务的时候,就需要填写任务类名为:com.zyxx.sbm.job.TestJob

    cron 表达式的知识这里就不一一介绍了

    2、页面添加定时任务

    添加定时任务

    那么我们的任务类名就是:com.zyxx.sbm.job.TestJob
    cron 表达式:*/2 * * * * ?,表示两秒钟执行一次
    参数:我们没有传入参数

    3、后台添加定时任务

    package com.zyxx.sbm.service.impl;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.zyxx.common.shiro.SingletonLoginUtils;
    import com.zyxx.common.utils.DateUtils;
    import com.zyxx.common.utils.LayTableResult;
    import com.zyxx.common.utils.ResponseResult;
    import com.zyxx.sbm.entity.SysQuartz;
    import com.zyxx.sbm.mapper.SysQuartzMapper;
    import com.zyxx.sbm.service.SysQuartzService;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.quartz.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * <p>
     * 定时任务信息表 服务实现类
     * </p>
     *
     * @author lizhou
     * @since 2020-07-21
     */
    @Slf4j
    @Service
    public class SysQuartzServiceImpl extends ServiceImpl<SysQuartzMapper, SysQuartz> implements SysQuartzService {
    
        @Autowired
        private Scheduler scheduler;
    
    	/**
    	* 添加定时任务
    	*/
        @Override
        public ResponseResult add(SysQuartz sysQuartz) {
            QueryWrapper<SysQuartz> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("class_name", sysQuartz.getClassName());
            List<SysQuartz> sysQuartzList = list(queryWrapper);
            if (null != sysQuartzList && !sysQuartzList.isEmpty()) {
                return ResponseResult.getInstance().error("该任务类名已经存在");
            }
            sysQuartz.setCreateTime(DateUtils.getYmdHms());
            sysQuartz.setCreateUser(SingletonLoginUtils.getUserId());
            save(sysQuartz);
            // 启动
            if (0 == sysQuartz.getQuartzStatus()) {
                this.schedulerAdd(sysQuartz.getClassName().trim(), sysQuartz.getCronExpression().trim(), sysQuartz.getParam());
            }
            return ResponseResult.getInstance().success();
        }
    
        /**
         * 添加定时任务
         *
         * @param className
         * @param cronExpression
         * @param param
         */
        @Override
        public void schedulerAdd(String className, String cronExpression, String param) {
            try {
                // 启动调度器
                scheduler.start();
                // 构建job信息
                JobDetail jobDetail = JobBuilder.newJob(getClass(className).getClass()).withIdentity(className).usingJobData("param", param).build();
                // 表达式调度构建器(即任务执行的时间)
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
                // 按新的cronExpression表达式构建一个新的trigger
                CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(className).withSchedule(scheduleBuilder).build();
                scheduler.scheduleJob(jobDetail, trigger);
            } catch (SchedulerException e) {
                log.error(e.getMessage());
            } catch (RuntimeException e) {
                log.error(e.getMessage());
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
    
        /**
         * 删除定时任务
         *
         * @param className
         */
        @Override
        public void schedulerDelete(String className) {
            try {
                scheduler.pauseTrigger(TriggerKey.triggerKey(className));
                scheduler.unscheduleJob(TriggerKey.triggerKey(className));
                scheduler.deleteJob(JobKey.jobKey(className));
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    
        private static Job getClass(String className) throws Exception {
            Class<?> class1 = Class.forName(className);
            return (Job) class1.newInstance();
        }
    }
    

    需要注入 Scheduler 对象,使用该对象开启或停止定时任务

    在启动定时任务之前,我们应先删除该任务类名开启的定时任务,防止该任务类名已经添加过了

    // 删除定时任务
    schedulerDelete(sysQuartz.getClassName().trim());
    // 添加定时任务
    schedulerAdd(sysQuartz.getClassName().trim(), sysQuartz.getCronExpression().trim(), sysQuartz.getParam());
    

    添加定时任务,传入任务类名,cron 表达式,参数

    停止定时任务,只需要:

    scheduler.pauseJob(JobKey.jobKey(sysQuartz.getClassName().trim()));
    

    根据任务类名,停止定时任务即可

    五、开发测试

    启动项目,在管理界面,开启定时任务,即可在控制台看到打印的信息
    打印信息
    表示我们的定时任务已经启动成功了

    六、优化建议

    当我们添加了定时任务并启动后,重新启动项目的时候,定时任务却不会自动启动,这时候,我们就需要在项目启动的时候做一些事情了,也就是系统启动任务

    不清楚的同学可以复习一下之前我的博客【SpringBoot】十九、SpringBoot中实现启动任务

    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.zyxx.sbm.entity.SysQuartz;
    import com.zyxx.sbm.service.SysQuartzService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import java.util.List;
    
    /**
     * @ClassName SystemStartTask
     * 项目启动任务--启动定时任务
     * @Author Lizhou
     * @Date 2020-07-21 12:56:56
     **/
    @Component
    @Order(100)
    public class SystemQuartzStartTask implements CommandLineRunner {
    
        @Autowired
        private SysQuartzService sysQuartzService;
    
        @Override
        public void run(String... args) throws Exception {
            // 查询启动的定时任务
            QueryWrapper<SysQuartz> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("status", 0);
            queryWrapper.eq("quartz_status", 0);
            List<SysQuartz> list = sysQuartzService.list(queryWrapper);
            if (null != list && !list.isEmpty()) {
                for (SysQuartz item : list) {
                    // 删除定时任务
                    sysQuartzService.schedulerDelete(item.getClassName().trim());
                    // 添加定时任务
                    sysQuartzService.schedulerAdd(item.getClassName().trim(), item.getCronExpression().trim(), item.getParam());
                }
            }
        }
    }
    

    从数据库查询出启动的定时任务,并将他们添加到定时任务启动中,这样项目一启动时,就会自动启动我们定义的定时任务了

    最后

    任务类名的正则表达式

    /^[a-zA-Z]+(\.([a-zA-Z])+)+$/
    

    cron 表达式的验证使用正则太麻烦,可以使用 Quartz 自带验证方法

    CronExpression.isValidExpression(cron)
    

    SpringBoot 中使用 Quartz 管理定时任务的学习就到这儿了,其实也并不难理解,相比于之前用的定时任务是不是好很多了呢,别忘了最后加上系统启动任务哦

    如您在阅读中发现不足,欢迎留言!!!

    更多相关内容
  • Quartz是OpenSymphony开源组织在Jobscheduling领域又一个开源项目,是完全由java开发的一个开源的任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他...

    定时任务调度

    什么是定时任务:

    • 定时任务是指调度程序在指定的时间或周期触发执行的任务
    • 使用场景:发送邮件、统计、状态修改、消息推送、活动开启、增量索引

    定时任务实现技术:

    • Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。使用较少。
    • ScheduledExecutorService:也是jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。
    • Spring3.0以后自主开发的定时任务工具spring task,使用简单,支持线程池,可以高效处理许多不同的定时任务,除spring相关的包外不需要额外的包,支持注解和配置文件两种形式。不能处理过于复杂的任务
    • 专业的定时框架quartz,功能强大,可以让你的程序在指定时间执行,也可以按照某一个频度执行,支持数据库、监听器、插件、集群。一般使用spring整合quartz或者springboot整合quartz。

    其他定时器

    Timer:

    /**
     * java timer 测试类
     * Created by gaozhy on 2017/6/24.
     */
    public class JavaTimer {
    
        public static void main(String[] args) {
            try {
                // 创建定时器
                Timer timer = new Timer();
    
                // 添加调度任务
                // 安排指定的任务在指定的时间开始进行重复的 固定延迟执行
                timer.schedule(new MyTask(),new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2017-06-24 22:31:10"),10*1000);
                // 安排指定的任务在指定的延迟后开始进行重复的 固定速率执行
                //timer.scheduleAtFixedRate(new MyTask(),new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2017-06-24 22:31:10"),10*1000);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 任务类
     * Created by gaozhy on 2017/6/24.
     */
    public class MyTask extends TimerTask{
    
        // 定义调度任务
        public void run() {
            System.out.println("log2:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        }
    }
    

    ScheduledExecutorService:

    • 该方法跟Timer类似,直接看demo:
    public class TestScheduledExecutorService {
    
       public static void main(String[] args) {
    
           ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
    
           // 参数:1、任务体 2、首次执行的延时时间
           //      3、任务执行间隔 4、间隔时间单位
           service.scheduleAtFixedRate(()->System.out.println("task ScheduledExecutorService "+new Date()), 0, 3, TimeUnit.SECONDS);
    
       }
    
    }
    

    Spring Task:

    • 基于配置:
    /**
     * Spring Task 任务类
     * Created by gaozhy on 2017/6/24.
     */
    
    public class SpringTask {
    
        private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        public void m1(){
            System.out.println("m1:"+simpleDateFormat.format(new Date()));
        }
    
        public void m2(){
            System.out.println("m2:"+simpleDateFormat.format(new Date()));
        }
    
        public void m3(){
            System.out.println("m2:"+simpleDateFormat.format(new Date()));
        }
    }
    
    <!--spring-task.xml配置-->
    <bean id="springTask" class="com.baizhi.task.SpringTask"></bean>
       <!--注册调度任务-->
       <task:scheduled-tasks>
           <!--延迟8秒 执行任务-->
           <!--<task:scheduled ref="springTask" method="m1" fixed-delay="8000" />-->
    
           <!--固定速度5秒 执行任务-->
           <!--<task:scheduled ref="springTask" method="m2" fixed-rate="5000"/>-->
    
           <!--
               使用cron表达式 指定触发时间
               spring task 只支持6位的cron表达式 秒 分 时 日 月 星期
           -->
           <task:scheduled ref="springTask" method="m3" cron="50-59 * * ? * *"/>
       </task:scheduled-tasks>
    
       <!--执行器配置-->
       <task:executor id="threadPoolTaskExecutor" pool-size="10" keep-alive="5"></task:executor>
    
       <!--调度器配置-->
       <task:scheduler id="threadPoolTaskScheduler" pool-size="10"></task:scheduler>
    
    • 基于注解
    /**
     * Spring Task 任务类
     * Created by gaozhy on 2017/6/24.
     */
    public class SpringAnnotationTask {
    
        private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
       /* @Scheduled(fixedDelay = 8000)
        public void m1(){
            System.out.println("m1:"+simpleDateFormat.format(new Date()));
        }
    
        @Scheduled(fixedRateString = "5000")
        public void m2(){
            System.out.println("m2:"+simpleDateFormat.format(new Date()));
        }
        */
        @Scheduled(cron = "0-30 * * * * ?")
        public void m3(){
            System.out.println("m2:"+simpleDateFormat.format(new Date()));
        }
    }
    
    <!--开启基于注解的spring task-->
    <task:annotation-driven></task:annotation-driven>
    
    <bean id="springAnnotationTask" class="com.baizhi.task.SpringAnnotationTask"></bean>
    

    Spring Task任务执行和调度(TaskExecutor,TaskSchedule):

    • 介绍:
      Spring Framework分别使用TaskExecutor和TaskScheduler接口提供异步执行和任务调度的抽象。Spring还具有支持线程池或在应用程序服务器环境中委托给CommonJ的接口的实现。最终,在公共接口背后使用这些实现抽象出了JavaSE 5,Java SE 6和Java EE环境之间的差异。
      ②Spring还提供了集成类,用于支持使用Timer(自1.3以来的JDK的一部分)和Quartz Scheduler(http://quartz-scheduler.org)进行调度。这两个调度程序都是使用FactoryBean设置的,它们分别具有对Timer或Trigger实例的可选引用。此外,还提供了Quartz Scheduler和Timer的便捷类,它允许您调用现有目标对象的方法(类似于正常的MethodInvokingFactoryBean操作)。
    • Spring官方对TaskExecutor的相关解释:
      Spring的TaskExecutor接口与java.util.concurrent.Executor接口相同。该接口具有单个方法(execute(Runnable task)),该方法根据线程池的语义和配置接受要执行的任务。
      ②TaskExecutor接口相关实现类:
    实现类名对应解释(直接甩翻译了)
    SyncTaskExecutor该实现类不会执行异步调用。 相反,每次调用都在调用的线程中进行(翻译过来也即同步任务执行器)。 它主要用于不需要多线程的情况,例如在简单的测试用例中。
    SimpleAsyncTaskExecutor此实现不会重用任何线程。 相反,它为每次调用启动一个新线程。 但是,它确实支持并发限制,该限制会阻止超出限制的任何调用,直到释放插槽为止。(说简单了,就是要使用了直接创建一个线程)
    ConcurrentTaskExecutor此实现是java.util.concurrent.Executor实例的适配器。很少需要直接使用ConcurrentTaskExecutor**(官网自己都觉得很少使用,不过相对于ThreadPoolTaskExecutor,官网推荐如果ThreadPoolTaskExecutor不够灵活,无法满足需求,则可以使用ConcurrentTaskExecutor)**。
    ThreadPoolTaskExecutor杀手锏级的任务调度器(最常用),可以说已经足够满足我们的需求了(除非,非常非常特例才使用ConcurrentTaskExecutor)。官网翻译重要片段:公开了bean属性,用于配置java.util.concurrent.ThreadP
    WorkManagerTaskExecutor此实现使用CommonJ WorkManager作为其后备服务提供程序,并且是在Spring应用程序上下文中在WebLogic或WebSphere上设置基于CommonJ的线程池集成的中心便利类。
    DefaultManagedTaskExecutor此实现在JSR-236兼容的运行时环境(例如Java EE 7+应用程序服务器)中使用JNDI获取的ManagedExecutorService,为此目的替换CommonJ WorkManager。(说明了就是依赖环境)
    • Spring官方对TaskSheduler接口的相关解释:
      用于在将来的某个时间点调度任务。
      ②TaskScheduler接口相关实现类:
    实现类名对应解释
    ConcurrentTaskScheduler该类实际继承了ConcurrentTaskExecutor对象。只是实现了TaskScheduler接口,增加了相关定时调度任务的方法。
    ThreadPoolTaskSchedulerspring对该类设计原则同ThreadPoolTaskExecutor类。是为了定时调度任务不依赖相关的运行容器(例如weblogic、WebSphere等)。其底层委托给ScheduledExecutorService,向外暴露相关的常见bean配置属性。
    • 总结:
      ①链接:
      <1>spring任务执行器与任务调度器
      <2>JAVA定时任务Timer、Spring Task、Quartz
      Quartz与Spring Task对比:
      <1>Quartz:
      1、默认多线程异步执行
      2、单个任务时,在上一个调度未完成时,下一个调度时间到时,会另起一个线程开始新的调度。业务繁忙时,一个任务会有多个调度,可能导致数据处理异常。
      3、多个任务时,任务之间没有直接影响,多任务执行的快慢取决于CPU的性能
      4、触发方式 : (1)SimpleTrigger (2)CronTrigger
      5、能被集群实例化,支持分布式部署
      6、使用JobStoreCMT(JDBCJobStore的子类),Quartz 能参与JTA事务;Quartz 能管理JTA事务(开始和提交)在执行任务之间,这样,任务做的事就可以发生在JTA事务里。
      <2>Task:
      1、默认单线程同步执行
      2、单个任务时,当前次的调度完成后,再执行下一次任务调度
      3、多个任务时,一个任务执行完成后才会执行下一个任务。若需要任务能够并发执行,需手动设置线程
      4、触发方式:与Quartz的CronTrigger的表达式类似,可以使用注解标注定时任务。

    Quartz简介

    Quartz简介:

    • Quartz是OpenSymphony开源组织在Jobscheduling领域又一个开源项目,是完全由java开发的一个开源的任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。
    • Quartz用一个小Java库发布文件(.jar文件),这个库文件包含了所有Quartz核心功能。这些功能的主要接口(API)是Scheduler接口。它提供了简单的操作,例如:将任务纳入日程或者从日程中取消,开始/停止/暂停日程进度。

    springboot定时任务(与Quartz整合):

    • 直接加入集成好的starter
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    

    定时器种类:

    • Quartz 中五种类型的Trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,NthIncludedDayTrigger和Calendar类( org.quartz.Calendar)。
    • 最常用的:
      ①SimpleTrigger:用来触发只需执行一次或者在给定时间触发并且重复N次且每次执行延迟一定时间的任务。
      ②CronTrigger:按照日历触发,例如“每个周五”,每个月10日中午或者10:15分。

    存储方式:

    • RAMJobStore和JDBCJobStore对比:
    类型优点缺点
    RAMJobStore不要外部数据库,配置容易,运行速度快因为调度程序信息是存储在被分配给JVM的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。另外因为存储到JVM内存里面,所以可以存储多少个Job和Trigger将会受到限制
    JDBCJobStore支持集群,因为所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务运行速度的快慢取决与连接数据库的快慢

    表关系和解释:

    • 表关系:
      在这里插入图片描述
    • 解释:
    表名称说明
    qrtz_blob_triggersTrigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore 并不知道如何存储实例的时候)
    qrtz_calendars以Blob类型存储Quartz的Calendar日历信息, quartz可配置一个日历来指定一个时间范围
    qrtz_cron_triggers存储Cron Trigger,包括Cron表达式和时区信息。
    qrtz_fired_triggers存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
    qrtz_job_details存储每一个已配置的Job的详细信息
    qrtz_locks存储程序的非观锁的信息(假如使用了悲观锁)
    qrtz_paused_trigger_graps存储已暂停的Trigger组的信息
    qrtz_scheduler_state存储少量的有关 Scheduler的状态信息,和别的 Scheduler 实例(假如是用于一个集群中)
    qrtz_simple_triggers存储简单的 Trigger,包括重复次数,间隔,以及已触的次数
    qrtz_triggers存储已配置的 Trigger的信息

    核心类和关系:

    • 核心类:
      QuartzSchedulerThread:负责执行向QuartzScheduler注册的触发Trigger的工作的线程。
      ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提供运行效率。
      QuartzSchedulerResources:包含创建QuartzScheduler实例所需的所有资源(JobStore,ThreadPool等)。
      SchedulerFactory :提供用于获取调度程序实例的客户端可用句柄的机制。
      JobStore: 通过类实现的接口,这些类要为org.quartz.core.QuartzScheduler的使用提供一个org.quartz.Job和org.quartz.Trigger存储机制。作业和触发器的存储应该以其名称和组的组合为唯一性。
      QuartzScheduler :这是Quartz的核心,它是org.quartz.Scheduler接口的间接实现,包含调度org.quartz.Jobs,注册org.quartz.JobListener实例等的方法。
      Scheduler :这是Quartz Scheduler的主要接口,代表一个独立运行容器。调度程序维护JobDetails和触发器的注册表。 一旦注册,调度程序负责执行作业,当他们的相关联的触发器触发(当他们的预定时间到达时)。
      Trigger :具有所有触发器通用属性的基本接口,描述了job执行的时间出发规则。 - 使用TriggerBuilder实例化实际触发器。
      JobDetail :传递给定作业实例的详细信息属性。 JobDetails将使用JobBuilder创建/定义。
      Job:要由表示要执行的“作业”的类实现的接口。只有一个方法 void execute(jobExecutionContext context)(jobExecutionContext 提供调度上下文各种信息,运行时数据保存在jobDataMap中)
      <1>Job有个子接口StatefulJob ,代表有状态任务。有状态任务不可并发,前次任务没有执行完,后面任务处于阻塞等到。
    • 关系:
      ①一个job可以被多个Trigger 绑定,但是一个Trigger只能绑定一个job!
      在这里插入图片描述

    配置文件:

    quartz.properties
    //调度标识名 集群中每一个实例都必须使用相同的名称 (区分特定的调度器实例)
    org.quartz.scheduler.instanceName:DefaultQuartzScheduler
    //ID设置为自动获取 每一个必须不同 (所有调度器实例中是唯一的)
    org.quartz.scheduler.instanceId :AUTO
    //数据保存方式为持久化 
    org.quartz.jobStore.class :org.quartz.impl.jdbcjobstore.JobStoreTX
    //表的前缀 
    org.quartz.jobStore.tablePrefix : QRTZ_
    //设置为TRUE不会出现序列化非字符串类到 BLOB 时产生的类版本问题
    //org.quartz.jobStore.useProperties : true
    //加入集群 true 为集群 false不是集群
    org.quartz.jobStore.isClustered : false
    //调度实例失效的检查时间间隔 
    org.quartz.jobStore.clusterCheckinInterval:20000 
    //容许的最大作业延长时间 
    org.quartz.jobStore.misfireThreshold :60000
    //ThreadPool 实现的类名 
    org.quartz.threadPool.class:org.quartz.simpl.SimpleThreadPool
    //线程数量 
    org.quartz.threadPool.threadCount : 10
    //线程优先级 
    org.quartz.threadPool.threadPriority : 5(threadPriority 属性的最大值是常量 java.lang.Thread.MAX_PRIORITY,等于10。最小值为常量 java.lang.Thread.MIN_PRIORITY,为1)
    //自创建父线程
    //org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true 
    //数据库别名
    org.quartz.jobStore.dataSource : qzDS
    //设置数据源
    org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver
    org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz
    org.quartz.dataSource.qzDS.user:root
    org.quartz.dataSource.qzDS.password:123456
    org.quartz.dataSource.qzDS.maxConnection:10
    

    JDBC插入表顺序:

    • 主要的JDBC操作类,执行sql顺序。
    Simple_trigger :插入顺序
    qrtz_job_details --->  qrtz_triggers --->  qrtz_simple_triggers
    qrtz_fired_triggers
    Cron_Trigger:插入顺序
    qrtz_job_details --->  qrtz_triggers --->  qrtz_cron_triggers
    qrtz_fired_triggers
    

    Quartz简单入门

    Job:

    public class MyJob implements Job{
    	private static final Logger log = LoggerFactory.getLogger(MyJob.class);
    
    	@Override
    	public void execute(JobExecutionContext context)throws JobExecutionException {
    		log.info("MyJob  is start ..................");
    		
    		log.info("Hello quzrtz  "+
    				new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ").format(new Date()));
    		
    		log.info("MyJob  is end .....................");
    	}
    }
    

    RAM方式:

    public class RAMQuartz {
    
    	private static Logger _log = LoggerFactory.getLogger(RAMQuartz.class);
    	
    	public static void main(String[] args) throws SchedulerException {
    		//1.创建Scheduler的工厂
    		SchedulerFactory sf = new StdSchedulerFactory();
    		//2.从工厂中获取调度器实例
    		Scheduler scheduler = sf.getScheduler();
    		
    		
    		//3.创建JobDetail
    		JobDetail jb = JobBuilder.newJob(MyJob.class)
    				.withDescription("this is a ram job") //job的描述
    				.withIdentity("ramJob", "ramGroup") //job 的name和group
    				.build();
    		
    		//任务运行的时间,SimpleSchedle类型触发器有效
    		long time=  System.currentTimeMillis() + 3*1000L; //3秒后启动任务
    		Date statTime = new Date(time);
    		
    		//4.创建Trigger
    			//使用SimpleScheduleBuilder或者CronScheduleBuilder
    		Trigger t = TriggerBuilder.newTrigger()
    					.withDescription("")
    					.withIdentity("ramTrigger", "ramTriggerGroup")
    					//.withSchedule(SimpleScheduleBuilder.simpleSchedule())
    					.startAt(statTime)  //默认当前时间启动
    					.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) //两秒执行一次
    					.build();
    		
    		//5.注册任务和定时器
    		scheduler.scheduleJob(jb, t);
    		
    		//6.启动 调度器
    		scheduler.start();
    		_log.info("启动时间 : " + new Date());
    			
    	}
    }
    

    JDBC方式:

    public class QuartzJdbcTest {
    	
    	public static void main(String[] args) throws SchedulerException,
    			ParseException {
    		startSchedule();
    		//resumeJob();
    	}
    	/**
    	 * 开始一个simpleSchedule()调度
    	 */
    	public static void startSchedule() {
    		try {
    			// 1、创建一个JobDetail实例,指定Quartz
    			JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
    			// 任务执行类
    					.withIdentity("job1_1", "jGroup1")
    					// 任务名,任务组
    					.build();
    		
    			
    			//触发器类型
    			SimpleScheduleBuilder builder = SimpleScheduleBuilder
    					// 设置执行次数
    				    .repeatSecondlyForTotalCount(5);
    			
    			//CronScheduleBuilder builder = CronScheduleBuilder.cronSchedule("0/2 * * * * ?");
    			// 2、创建Trigger
    			Trigger trigger = TriggerBuilder.newTrigger()
    					.withIdentity("trigger1_1", "tGroup1").startNow()
    					.withSchedule(builder)
    					.build();
    			
    			// 3、创建Scheduler
    			Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    			scheduler.start();
    			// 4、调度执行
    			scheduler.scheduleJob(jobDetail, trigger);
    			try {
    				Thread.sleep(60000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    
    			//关闭调度器
    			scheduler.shutdown();
    
    		} catch (SchedulerException e) {
    			e.printStackTrace();
    		}
    	}
    
    	/**
    	 * 从数据库中找到已经存在的job,并重新开户调度
    	 */
    	public static void resumeJob() {
    		try {
    
    			SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    			Scheduler scheduler = schedulerFactory.getScheduler();
    			JobKey jobKey = new JobKey("job1_1", "jGroup1");
    			List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
    			//SELECT TRIGGER_NAME, TRIGGER_GROUP FROM {0}TRIGGERS WHERE SCHED_NAME = {1} AND JOB_NAME = ? AND JOB_GROUP = ?
    			// 重新恢复在jGroup1组中,名为job1_1的 job的触发器运行
    			if(triggers.size() > 0){
    				for (Trigger tg : triggers) {
    					// 根据类型判断
    					if ((tg instanceof CronTrigger) || (tg instanceof SimpleTrigger)) {
    						// 恢复job运行
    						scheduler.resumeJob(jobKey);
    					}
    				}
    				scheduler.start();
    			}
    			
    		} catch (Exception e) {
    			e.printStackTrace();
    
    		}
    	}
    }
    

    Spring和Quartz集成

    RAM存储方式的xml配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
    	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    	http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
    
    	<!-- 
    		Spring整合Quartz进行配置遵循下面的步骤:
    		1:定义工作任务的Job
    		2:定义触发器Trigger,并将触发器与工作任务绑定
    		3:定义调度器,并将Trigger注册到Scheduler
    	 -->
    	<!-- 1:定义任务的bean ,这里使用JobDetailFactoryBean,也可以使用MethodInvokingJobDetailFactoryBean ,配置类似-->
    	<bean name="hwJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    		<!-- 指定job的名称 -->
    		<property name="name" value="hw_job"/>
    		<!-- 指定job的分组 -->
    		<property name="group" value="hw_group"/>
    		<!-- 指定具体的job类 -->
    		<property name="jobClass" value="com.dufy.spring.quartz.chapter01.job.HelloWorldJob"/>
    		<!-- 必须设置为true,如果为false,当没有活动的触发器与之关联时会在调度器中会删除该任务  -->
    		<property name="durability" value="true"/>
    		<!-- 指定spring容器的key,如果不设定在job中的jobmap中是获取不到spring容器的 -->
    		<property name="applicationContextJobDataKey" value="applicationContext"/>
    	</bean>
    	<!-- 2.1:定义触发器的bean,定义一个Simple的Trigger,一个触发器只能和一个任务进行绑定 -->
    	<!-- <bean name="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    		指定Trigger的名称
    		<property name="name" value="hw_trigger"/>
    		指定Trigger的名称
    		<property name="group" value="hw_trigger_group"/>
    		指定Tirgger绑定的Job
    		<property name="jobDetail" ref="hwJob"/>
    		指定Trigger的延迟时间 1s后运行
    		<property name="startDelay" value="1000"/>
    		指定Trigger的重复间隔  5s
    		<property name="repeatInterval" value="5000"/>
    		指定Trigger的重复次数
    		<property name="repeatCount" value="5"/>
    	</bean> -->
    	
    	<!-- 2.2:定义触发器的bean,定义一个Cron的Trigger,一个触发器只能和一个任务进行绑定 -->
    	<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    		<!-- 指定Trigger的名称 -->
    		<property name="name" value="hw_trigger"/>
    		<!-- 指定Trigger的名称 -->
    		<property name="group" value="hw_trigger_group"/>
    		<!-- 指定Tirgger绑定的Job -->
    		<property name="jobDetail" ref="hwJob"/>
    		<!-- 指定Cron 的表达式 ,当前是每隔1s运行一次 -->
    		<property name="cronExpression" value="0/1 * * * * ?" />
    	</bean>
    	
    	
    	<!-- 3.定义调度器,并将Trigger注册到调度器中 -->
    	<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    		<property name="triggers">
    			<list>
    				<!-- <ref bean="simpleTrigger"/> -->
    				<ref bean="cronTrigger"/>
    			</list>
    		</property>
    		<!-- <property name="autoStartup" value="true" /> -->
    	</bean>
    	
    </beans>
    

    JDBC存储方式的xml配置文件:

    • Spring配置文件:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
    	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    	http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
    
    
    	<!-- =========JDBC版=========== -->
    	<!-- 
    		持久化数据配置,需要添加quartz.properties
    	 -->
    	<bean name="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
            <property name="applicationContextSchedulerContextKey" value="applicationContextKey"/>
            <property name="configLocation" value="classpath:quartz.properties"/>	
    	</bean>
    	
    </beans>
    
    • quartz.properties:
    # Default Properties file for use by StdSchedulerFactory
    # to create a Quartz Scheduler Instance, if a different
    # properties file is not explicitly specified.
    #
    
    #============================================================================
    # Configure Main Scheduler Properties
    #============================================================================
    org.quartz.scheduler.instanceName: dufy_test
    org.quartz.scheduler.instanceId = AUTO
    
    org.quartz.scheduler.rmi.export: false
    org.quartz.scheduler.rmi.proxy: false
    org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
    #============================================================================
    # Configure ThreadPool
    #============================================================================
    org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount: 2
    org.quartz.threadPool.threadPriority: 5
    org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
    
    org.quartz.jobStore.misfireThreshold: 60000
    #============================================================================
    # Configure JobStore
    #============================================================================
     
    #default config
    #org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
    #持久化配置
    org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
    org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    org.quartz.jobStore.useProperties:true
    
    #============================================================================
    #havent cluster spring
    #============================================================================
    org.quartz.jobStore.isClustered = false  
    
    #数据库表前缀
    org.quartz.jobStore.tablePrefix:qrtz_
    org.quartz.jobStore.dataSource:qzDS
    
    #============================================================================
    # Configure Datasources
    #============================================================================
    #JDBC驱动
    org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver
    org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz_test
    org.quartz.dataSource.qzDS.user:root
    org.quartz.dataSource.qzDS.password:root
    org.quartz.dataSource.qzDS.maxConnection:10
    

    SSMM和Quartz集成

    配置文件介绍:

    • 在ApplicationContext.xml中添加下面的配置:
      ①dataSource:配置了dataSource会读取spring配置的数据库,不会去加载quartz.properties配置的数据源。
      ②applicationContextSchedulerContextKey:配置这个可以获取spring容器中的context。
      ③加载quartz配置文件信息。
    <bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" >
       <property name="dataSource" ref ="dataSource" />       
       <property name="applicationContextSchedulerContextKey" value="applicationContextKey"/>
       <property name="configLocation" value="classpath:quartz.properties"/>			
    </bean>
    
    • 添加quartz.properties配置文件:
    # Default Properties file for use by StdSchedulerFactory
    # to create a Quartz Scheduler Instance, if a different
    # properties file is not explicitly specified.
    #
    
    #============================================================================
    # Configure Main Scheduler Properties
    #============================================================================
    org.quartz.scheduler.instanceName: quartzScheduler
    org.quartz.scheduler.instanceId = AUTO
    
    org.quartz.scheduler.rmi.export: false
    org.quartz.scheduler.rmi.proxy: false
    org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
    #============================================================================
    # Configure ThreadPool
    #============================================================================
    org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount: 2
    org.quartz.threadPool.threadPriority: 5
    org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
    
    org.quartz.jobStore.misfireThreshold: 60000
    #============================================================================
    # Configure JobStore
    #============================================================================
     
    #default config
    #org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
    #持久化配置
    org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
    org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    org.quartz.jobStore.useProperties:true
    
    #============================================================================
    #havent cluster spring
    #============================================================================
    org.quartz.jobStore.isClustered = false  
    
    #数据库表前缀
    org.quartz.jobStore.tablePrefix:qrtz_
    #org.quartz.jobStore.dataSource:qzDS
    
    #============================================================================
    # Configure Datasources
    #============================================================================
    #JDBC驱动  Sping去管理dataSource ,这里不在配置数据源信息
    #org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver
    #org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz_test
    #org.quartz.dataSource.qzDS.user:root
    #org.quartz.dataSource.qzDS.password:root
    #org.quartz.dataSource.qzDS.maxConnection:10
    

    BAO和Service接口:

    • Dao主要是用户的一些操作:
    public interface IUserDao {
        int deleteByPrimaryKey(Integer id);
    
        int insert(User record);
    
        int insertSelective(User record);
    
        User selectByPrimaryKey(Integer id);
    
        int updateByPrimaryKeySelective(User record);
    
        int updateByPrimaryKey(User record);
        
        User findUser(User user);
    }
    
    • Service主要是对定时任务的一些操作:
    package org.ssm.dufy.service;
    
    public interface QuartzService {
    
    	/**
    	 * addJob(方法描述:添加一个定时任务) <br />
    	 * (方法适用条件描述: – 可选)
    	 * 
    	 * @param jobName
    	 *            作业名称
    	 * @param jobGroupName
    	 *            作业组名称
    	 * @param triggerName
    	 *            触发器名称
    	 * @param triggerGroupName
    	 *            触发器组名称
    	 * @param cls
    	 *            定时任务的class
    	 * @param cron
    	 *            时间表达式 void
    	 * @exception
    	 * @since 1.0.0
    	 */
    	public void addJob(String jobName, String jobGroupName,String triggerName, String triggerGroupName, Class cls, String cron);
    
    	/**
    	 * 
    	 * @param oldjobName 原job name
    	 * @param oldjobGroup 原job group
    	 * @param oldtriggerName 原 trigger name
    	 * @param oldtriggerGroup 原 trigger group
    	 * @param jobName
    	 * @param jobGroup
    	 * @param triggerName
    	 * @param triggerGroup
    	 * @param cron
    	 */
    	public boolean modifyJobTime(String oldjobName,String oldjobGroup, String oldtriggerName, String oldtriggerGroup, String jobName, String jobGroup,String triggerName, String triggerGroup, String cron);
    
    	/**
    	 * 修改触发器调度时间
    	 * @param triggerName  触发器名称
    	 * @param triggerGroupName  触发器组名称
    	 * @param cron cron表达式
    	 */
    	public void modifyJobTime(String triggerName,
    			String triggerGroupName, String cron);
    
    	
    	/**
    	 * 暂停指定的任务
    	 * @param jobName 任务名称
    	 * @param jobGroupName 任务组名称 
    	 * @return
    	 */
    	public void pauseJob(String jobName,String jobGroupName);
    	
    	/**
    	 * 恢复指定的任务
    	 * @param jobName 任务名称
    	 * @param jobGroupName 任务组名称 
    	 * @return
    	 */
    	public void resumeJob(String jobName,String jobGroupName);
    	
    	/**
    	 * 删除指定组任务
    	 * @param jobName 作业名称
    	 * @param jobGroupName 作业组名称
    	 * @param triggerName 触发器名称
    	 * @param triggerGroupName 触发器组名称
    	 */
    	public void removeJob(String jobName, String jobGroupName,
    			String triggerName, String triggerGroupName);
    
    	
    	/**
    	 * 开始所有定时任务。启动调度器
    	 */
    	public void startSchedule();
    
    	/**
    	 * 关闭调度器
    	 */
    	public void shutdownSchedule();
    }
    

    Quartz集群

    Quartz集群原理与架构:

    • 一个Quartz集群中的每个节点是一个独立的Quartz应用,它又管理着其他的节点。这就意味着你必须对每个节点分别启动或停止。Quartz集群中,独立的Quartz节点并不与另一其的节点或是管理节点通信,而是通过相同的数据库表来感知到另一Quartz应用的。
    • 集群通过故障切换和负载平衡的功能,能给调度器带来高可用性和伸缩性。目前集群只能工作在JDBC-JobStore(JobStore TX或者JobStoreCMT)方式下,从本质上来说,是使集群上的每一个节点通过共享同一个数据库来工作的( Quartz通过启动两个维护线程来维护数据库状态实现集群管理,一个是检测节点状态线程,一个是恢复任务线程 )。
      ①负载平衡是自动完成的,集群的每个节点会尽快触发任务。当一个触发器的触发时间到达时,第一个节点将会获得任务(通过锁定),成为执行任务的节点。
      ②故障切换的发生是在当一个节点正在执行一个或者多个任务失败的时候。当一个节点失败了,其他的节点会检测到并且标 识在失败节点上正在进行的数据库中的任务。任何被标记为可恢复(任务详细信息的"requests recovery"属性)的任务都会被其他的节点重新执行。没有标记可恢复的任务只会被释放出来,将会在下次相关触发器触发时执行。
      在这里插入图片描述

    注意事项:

    • 时间同步问题:
      ①Quartz实际并不关心你是在相同还是不同的机器上运行节点。当集群放置在不同的机器上时,称之为水平集群。节点跑在同一台机器上时,称之为垂直集群。对于垂直集群,存在着单点故障的问题。这对高可用性的应用来说是无法接受的,因为一旦机器崩溃了,所有的节点也就被终止了。对于水平集群,存在着时间同步问题。
      ②节点用时间戳来通知其他实例它自己的最后检入时间。假如节点的时钟被设置为将来的时间,那么运行中的Scheduler将再也意识不到那个结点已经宕掉了。另一方面,如果某个节点的时钟被设置为过去的时间,也许另一节点就会认定那个节点已宕掉并试图接过它的Job重运行。最简单的同步计算机时钟的方式是使用某一个Internet时间服务器(Internet Time Server ITS)。
    • 节点争抢Job问题:
      ①因为Quartz使用了一个随机的负载均衡算法, Job以随机的方式由不同的实例执行。Quartz官网上提到当前,还不存在一个方法来指派(钉住) 一个 Job 到集群中特定的节点。
    • 从集群获取Job列表问题:
      ①当前,如果不直接进到数据库查询的话,还没有一个简单的方式来得到集群中所有正在执行的Job列表。请求一个Scheduler实例,将只能得到在那个实例上正运行Job的列表。Quartz官网建议可以通过写一些访问数据库JDBC代码来从相应的表中获取全部的Job信息。

    Quartz配置

    quartz.properties:

    #quartz集群配置  
    #调度标识名 集群中每一个实例都必须使用相同的名称    
    org.quartz.scheduler.instanceName=DefaultQuartzScheduler
    #ID设置为自动获取 每一个必须不同  
    org.quartz.scheduler.instanceId=AUTO
    org.quartz.scheduler.makeSchedulerThreadDaemon=true
    #线程池的实现类(一般使用SimpleThreadPool即可满足需求)  
    org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
    #指定在线程池里面创建的线程是否是守护线程
    org.quartz.threadPool.makeThreadsDaemons=true
    #指定线程数,至少为1(无默认值)
    org.quartz.threadPool.threadCount:20
    #设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5) 
    org.quartz.threadPool.threadPriority:5
    #数据保存方式为数据库持久化  
    org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
    #数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库  
    org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    #表的前缀,默认QRTZ_  
    org.quartz.jobStore.tablePrefix=QRTZ_
    #是否加入集群
    org.quartz.jobStore.isClustered=true
    # 信息保存时间 默认值60秒   
    org.quartz.jobStore.misfireThreshold=25000
    

    Quartz分布式

    分布式定时任务:

    • 什么是分布式定时任务:把分散的,可靠性差的计划任务纳入统一的平台,并实现集群管理调度和分布式部署的一种定时任务的管理方式。叫做分布式定时任务。
    • 常见开源方案:
      ①elastic-job
      ②xxl-job
      ③quartz
      ④saturn
      ⑤opencron
      ⑥antares
    • 链接:分布式定时任务
    featurequartzelastic-job-cloudxxl-jobantaresopencron
    依赖mysqljdk1.7+, zookeeper 3.4.6+ ,maven3.0.4+ ,mesosmysql ,jdk1.7+ , maven3.0+jdk 1.7+ , redis , zookeeperjdk1.7+ , Tomcat8.0+
    HA多节点部署,通过竞争数据库锁来保证只有一个节点执行任务通过zookeeper的注册与发现,可以动态的添加服务器。支持水平扩容集群部署集群部署
    任务分片支持支持支持
    文档完善完善完善完善文档略少文档略少
    管理界面支持支持支持支持
    难易程度简单较复杂简单一般一般
    公司OpenSymphony当当网个人个人个人
    高级功能弹性扩容,多种作业模式,失效转移,运行状态收集,多线程处理数据,幂等性,容错处理,spring命名空间支持弹性扩容,分片广播,故障转移,Rolling实时日志,GLUE(支持在线编辑代码,免发布),任务进度监控,任务依赖,数据加密,邮件报警,运行报表,国际化任务分片, 失效转移,弹性扩容 ,时间规则支持quartz和crontab ,kill任务, 现场执行,查询任务运行状态
    缺点没有管理界面,以及不支持任务分片等。不适用于分布式场景需要引入zookeeper , mesos, 增加系统复杂度, 学习成本较高调度中心通过获取 DB锁来保证集群中执行任务的唯一性, 如果短任务很多,随着调度中心集群数量增加,那么数据库的锁竞争会比较厉害,性能不好。不支持动态添加任务不适用于分布式场景
    使用企业大众化产品,对分布式调度要求不高的公司大面积使用36氪,当当网,国美,金柚网,联想,唯品会,亚信,平安,猪八戒大众点评,运满满,优信二手车,拍拍贷

    分布式定时任务方案:

    • 方案一:单独设置任务调度服务
      ①任务调度服务部署在单结点,定时任务以Http请求的方式去向网关调用任务请求,网关将请求分发到集群中的某一个节点,某一个节点执行任务。
    • 方案二:使用Redis分布式锁实现
      ①每个定时任务都在Redis中设置一个Key-Value的分布式锁,Key为自定义的每个定时任务的名字(如task1:redis:lock),Value为服务器Ip,同时设置合适的过期时间(例如设置为5min),只有获取到锁的服务才能执行任务,其他服务则只能等待轮询。
      ②每个节点在执行时,都要进行以下操作:
      1.是否存在Key,若不存在,则设置Key-Value,Value为当前节点的IP
      2.若存在Key,则比较Value是否是当前Ip,若是则获取到了锁继续执行定时任务,若不是,则没有获取到锁就不往下执行并定时任务轮询 直到它抢到锁为止。
    • 方案三:开源分布式框架(推荐)
      ①使用XXL-JOB实现
      xxl-job是一个轻量级的分布式任务调度框架,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。
      <1>xxl-job的设计思想为:
      (1)将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求
      (2)将任务抽象成分散的JobHandler,交由执行器统一管理,执行器负责接收调度请求并执行对应的JobHandler中业务逻辑
      <2>因此,“调度”和“任务”可以互相解偶,提高系统整体的稳定性和扩展性。
      ②Elastic-Job:Elastic-job在2.x之后,出现了两个相互独立的产品线:Elastic-job-lite和Elastic-job-cloud
      <1>Elastic-job-lite:定位为轻量级无中心化的解决方案,使用jar包的形式提供分布式任务的协调服务,外部依赖仅依赖于zookeeper。

    xxl-job分布式任务调度原理:

    Job与Tigger状态

    简介:

    • jobkey相当于一把钥匙连接 所有从 schedule 中 获取 信息的钥匙,你会获得 当前 钥匙 下 的初始化信息
    JobKey jobkey = new JobKey(name,group)
    scheduler.getJobDetail(jobKey).getJobDataMap().get("当时创建的名称")
    
    • 获取 该条记录下次 着火事件的时间
    Date date = scheduler.getTriggersOfJob(jobKey).get(0).getNextFireTime();
    
    • 获得 该job 的 调度状态,也可以使用 jobkey
     TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup());
     scheduler.getTriggerState(triggerKey);
    
    • 状态情况分为以下几类 可以直接拿去用。
    private static String getTriggerStatesCN(String key) {
        Map<String, String> map = new LinkedHashMap<String, String>();
        map.put("BLOCKED", "阻塞");
        map.put("COMPLETE", "完成");
        map.put("ERROR", "出错");
        map.put("NONE", "不存在");
        map.put("NORMAL", "正常");
        map.put("PAUSED", "暂停");
    
        map.put("4", "阻塞");
        map.put("2", "完成");
        map.put("3", "出错");
        map.put("-1", "不存在");
        map.put("0", "正常");
        map.put("1", "暂停");
        /*  **STATE_BLOCKED 4 阻塞
    STATE_COMPLETE 2 完成
    STATE_ERROR 3 错误
    STATE_NONE -1 不存在
    STATE_NORMAL 0 正常
    STATE_PAUSED 1 暂停***/
        return map.get(key);
    }
    

    在这里插入图片描述

    quartz 监听器

    quartz 监听器主要分三大类:

    • SchedulerListener:任务调度监听器
    • TriggerListener:任务触发监听器
    • JobListener:任务执行监听器

    SchedulerListener:

    • SchedulerListener监听器,主要对任务调度Scheduler生命周期中关键节点进行监听,它只能全局进行监听,简单示例如下:
    public class SimpleSchedulerListener implements SchedulerListener {
    
        @Override
        public void jobScheduled(Trigger trigger) {
            System.out.println("任务被部署时被执行");
        }
    
        @Override
        public void jobUnscheduled(TriggerKey triggerKey) {
            System.out.println("任务被卸载时被执行");
        }
    
        @Override
        public void triggerFinalized(Trigger trigger) {
            System.out.println("任务完成了它的使命,光荣退休时被执行");
        }
    
        @Override
        public void triggerPaused(TriggerKey triggerKey) {
            System.out.println(triggerKey + "(一个触发器)被暂停时被执行");
        }
    
        @Override
        public void triggersPaused(String triggerGroup) {
            System.out.println(triggerGroup + "所在组的全部触发器被停止时被执行");
        }
    
        @Override
        public void triggerResumed(TriggerKey triggerKey) {
            System.out.println(triggerKey + "(一个触发器)被恢复时被执行");
        }
    
        @Override
        public void triggersResumed(String triggerGroup) {
            System.out.println(triggerGroup + "所在组的全部触发器被回复时被执行");
        }
    
        @Override
        public void jobAdded(JobDetail jobDetail) {
            System.out.println("一个JobDetail被动态添加进来");
        }
    
        @Override
        public void jobDeleted(JobKey jobKey) {
            System.out.println(jobKey + "被删除时被执行");
        }
    
        @Override
        public void jobPaused(JobKey jobKey) {
            System.out.println(jobKey + "被暂停时被执行");
        }
    
        @Override
        public void jobsPaused(String jobGroup) {
            System.out.println(jobGroup + "(一组任务)被暂停时被执行");
        }
    
        @Override
        public void jobResumed(JobKey jobKey) {
            System.out.println(jobKey + "被恢复时被执行");
        }
    
        @Override
        public void jobsResumed(String jobGroup) {
            System.out.println(jobGroup + "(一组任务)被恢复时被执行");
        }
    
        @Override
        public void schedulerError(String msg, SchedulerException cause) {
            System.out.println("出现异常" + msg + "时被执行");
            cause.printStackTrace();
        }
    
        @Override
        public void schedulerInStandbyMode() {
            System.out.println("scheduler被设为standBy等候模式时被执行");
        }
    
        @Override
        public void schedulerStarted() {
            System.out.println("scheduler启动时被执行");
        }
    
        @Override
        public void schedulerStarting() {
            System.out.println("scheduler正在启动时被执行");
        }
    
        @Override
        public void schedulerShutdown() {
            System.out.println("scheduler关闭时被执行");
        }
    
        @Override
        public void schedulerShuttingdown() {
            System.out.println("scheduler正在关闭时被执行");
        }
    
        @Override
        public void schedulingDataCleared() {
            System.out.println("scheduler中所有数据包括jobs, triggers和calendars都被清空时被执行");
        }
    }
    
    • 需要在任务调度器启动前,将SimpleSchedulerListener注册到Scheduler容器中:
    // 创建一个Scheduler
    Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    //添加SchedulerListener监听器
    scheduler.getListenerManager().addSchedulerListener(new SimpleSchedulerListener());
    // 启动Scheduler
    scheduler.start();
    

    TriggerListener:

    • TriggerListener,与触发器Trigger相关的事件都会被监听,它既可以全局监听,也可以实现局部监听。所谓局部监听,就是对某个Trigger的名称或者组进行监听,简单示例如下:
    public class SimpleTriggerListener implements TriggerListener {
    
        /**
         * Trigger监听器的名称
         * @return
         */
        @Override
        public String getName() {
            return "mySimpleTriggerListener";
        }
    
        /**
         * Trigger被激发 它关联的job即将被运行
         * @param trigger
         * @param context
         */
        @Override
        public void triggerFired(Trigger trigger, JobExecutionContext context) {
            System.out.println("myTriggerListener.triggerFired()");
        }
    
        /**
         * Trigger被激发 它关联的job即将被运行, TriggerListener 给了一个选择去否决 Job 的执行,如果返回TRUE 那么任务job会被终止
         * @param trigger
         * @param context
         * @return
         */
        @Override
        public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
            System.out.println("myTriggerListener.vetoJobExecution()");
            return false;
        }
    
        /**
         * 当Trigger错过被激发时执行,比如当前时间有很多触发器都需要执行,但是线程池中的有效线程都在工作,
         * 那么有的触发器就有可能超时,错过这一轮的触发。
         * @param trigger
         */
        @Override
        public void triggerMisfired(Trigger trigger) {
            System.out.println("myTriggerListener.triggerMisfired()");
        }
    
        /**
         * 任务完成时触发
         * @param trigger
         * @param context
         * @param triggerInstructionCode
         */
        @Override
        public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
            System.out.println("myTriggerListener.triggerComplete()");
        }
    }
    
    • 与之类似,需要将SimpleTriggerListener注册到Scheduler容器中!
    // 创建一个Scheduler
    Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    // 创建并注册一个全局的Trigger Listener
    scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener(), EverythingMatcher.allTriggers());
            
    // 创建并注册一个局部的Trigger Listener
    //scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener(), KeyMatcher.keyEquals(TriggerKey.triggerKey("myTrigger", "myJobTrigger")));
            
    // 创建并注册一个特定组的Trigger Listener
    //scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener(), GroupMatcher.groupEquals("myTrigger"));
    // 启动Scheduler
    scheduler.start();
    

    JobListener:

    • JobListener,与任务执行Job相关的事件都会被监听,和Trigger一样,既可以全局监听,也可以实现局部监听。
    public class SimpleJobListener implements JobListener {
    
        /**
         * job监听器名称
         * @return
         */
        @Override
        public String getName() {
            return "mySimpleJobListener";
        }
    
        /**
         * 任务被调度前
         * @param context
         */
        @Override
        public void jobToBeExecuted(JobExecutionContext context) {
            System.out.println("simpleJobListener监听器,准备执行:"+context.getJobDetail().getKey());
        }
    
        /**
         * 任务调度被拒了
         * @param context
         */
        @Override
        public void jobExecutionVetoed(JobExecutionContext context) {
            System.out.println("simpleJobListener监听器,取消执行:"+context.getJobDetail().getKey());
        }
    
        /**
         * 任务被调度后
         * @param context
         * @param jobException
         */
        @Override
        public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
            System.out.println("simpleJobListener监听器,执行结束:"+context.getJobDetail().getKey());
        }
    }
    
    • 同样的,将SimpleJobListener注册到Scheduler容器中,即可实现监听!
    // 创建一个Scheduler
    Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    // 创建并注册一个全局的Job Listener
    scheduler.getListenerManager().addJobListener(new SimpleJobListener(), EverythingMatcher.allJobs());
    
    // 创建并注册一个指定任务的Job Listener
    //scheduler.getListenerManager().addJobListener(new SimpleJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("myJob", "myJobGroup")));
    
    // 将同一任务组的任务注册到监听器中
    //scheduler.getListenerManager().addJobListener(new SimpleJobListener(), GroupMatcher.jobGroupEquals("myJobGroup"));
    
    // 启动Scheduler
    scheduler.start();
    

    Quartz总结

    Quartz总结:

    • quartz 已经有对应的任务表,不需要手动创建,只需要配置quartz.properties文件即可!quartz官方提供了对应任务表,主要用于分布式架构下的任务处理!如果只是单体应用,可以创建一张单表来存储任务,实现简单!
    • 如果只在单体环境中应用,Quartz 未必是最好的选择,例如Spring Scheduled一样也可以实现任务调度,并且与SpringBoot无缝集成,支持注解配置,非常简单,但是它有个缺点就是在集群环境下,会导致任务被重复调度!
    • quartz分布式架构和集群架构其实是一样的,都是部署多台定时任务机器而已。(不同于应用服务器的集群和分布式)
    • 链接:
      Quartz 架构和单体应用介绍
      SpringBoot 整合 Quartz 实现分布式调度
      Quartz详解

    Quartz注意:

    • Quartz 当 Job 执行时间超过触发间隔时间时所发生的情况:
      ①默认情况下,当Job执行时间超过间隔时间时,调度框架为了能让任务按照我们预定的时间间隔执行,会马上启用新的线程执行任务。
      ②若我们希望当前任务执行完之后再执行下一轮任务,也就是不要并发执行任务,该如何解决呢?
      <1>设置 quartz.threadPool.threadCount 最大线程数为 1。这样到了第二次执行任务时,若当前还没执行完,调度器想新开一个线程执行任务,但我们却设置了最大线程数为 1 个(即:没有足够的线程提供给调度器),则调度器会等待当前任务执行完之后,再立即调度执行下一次任务。(注意:此方法仅适用于Quartz中仅有一个Job,如果有多个Job,会影响其他Job的执行)
      <2>在 Job 类的头部加上 [DisallowConcurrentExecution],表示禁用并发执行。(推荐使用此方法)
    展开全文
  • Quartz任务调度(详细)

    千次阅读 2021-07-15 15:25:05
    Quartz任务调度 一、Quartz概念 Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合,也可以单独使用。 Quartz是开源且具有丰富特性的“任务调度库”,能够集成于...

    一、Quartz概念

    1. 基本介绍

    Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合,也可以单独使用。

    Quartz是开源且具有丰富特性的“任务调度库”,能够集成于任何的Java应用,小到独立的应用,大至电子商业系统。Quartz能够创建亦简单亦复杂的调度,以执行上十、上百,甚至上万的任务。任务job被定义为标准的Java组件,能够执行任何你想要实现的功能。Quartz调度框架包含许多企业级的特性,如JTA事务、集群的支持。

    简而言之,Quartz就是基于Java实现的任务调度框架,用于执行你想要执行的任何任务。

    官方网址:http://www.quartz-scheduler.org/
    官方文档:http://www.quartz-scheduler.org/documentation/
    原码地址:https://github.com/quartz-scheduler/quartz

    2. Quartz运行环境

    • Quartz可以运行嵌入在另一个独立式应用程序
    • Quartz可以在应用程序服务器(或Servlet容器)内被实例化,并且参与事务
    • Quartz可以作为一个独立的程序运行(其自己的Java虚拟机内),可以通过RMI使用
    • Quartz可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行

    3. Quartz核心概念

    • 任务 Job

      Job 就是你想要实现的任务类,每一个 Job 必须实现 org.quartz.job 接口,且只需实现接口定义的 execute() 方法。

    • 触发器 Trigger

      Trigger 为你执行任务的触发器,比如你想每天定时3点发送一份统计邮件,Trigger 将会设置3点执行该任务。
      Trigger 主要包含两种 SimplerTrigger 和 CronTrigger 两种。详见 7.9 与 7.10

    • 调度器 Scheduler

      Scheduler 为任务的调度器,它会将任务 Job 及触发器 Trigger 整合起来,负责基于 Trigger 设定的时间来执行 Job。

    4. Quartz的体系结构

    在这里插入图片描述

    二、Quart的使用

    1、引入Quartz的jar包

    创建一个 springboot(版本:2.2.4.RELEASE) 应用,直接引入依赖即可

    <dependencies>
    	<!-- Quartz 核心包 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
        </dependency>
    
    	<!-- Quartz 工具包 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
        </dependency>
    </dependencies>
    

    2、入门案例

    (1)创建HelloJob任务类

    // 定义任务类
    public class HelloJob implements Job {
    
        @Override
        public void execute(JobExecutionContext arg0) throws JobExecutionException {
            // 输出当前时间
            ystem.out.println(new Date());
        }
    }
    

    (2)创建任务调度类HelloSchedulerDemo

    public class HelloSchedulerDemo {
    
        public static void main(String[] args) throws Exception {
            // 1、调度器(Scheduler),从工厂中获取调度的实例(默认:实例化new StdSchedulerFactory();)
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    
            // 2、任务实例(JobDetail)定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
            JobDetail jobDetail = JobBuilder.newJob() // 加载任务类,与HelloJob完成绑定,要求HelloJob实现Job接口
                    .withIdentity("job1", "group1") // 参数1:任务的名称(唯一实例);参数2:任务组的名称
                    .build();
    
            // 3、触发器(Trigger)定义触发器,马上执行,然后每5秒重复执行一次
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1") // 参数1:触发器的名称(唯一实例);参数2:触发器组的名称
                    .startNow() // 马上启动触发器
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()) // 每2秒重复执行一次
                    .build();
    
            // 4、让调度器关联任务和触发器,保证按照触发器定义的调整执行任务
            scheduler.scheduleJob(jobDetail, trigger);
    
            // 5、启动
            scheduler.start();
            // 关闭
            //scheduler.shutdown();
        }
    
    }
    

    3、Job和JobDetail详解

    • Job:工作任务调度的接口,任务需要实现该接口。该接口中定义execute方法,类似JDK提供的TimeTask类的run方法。在里面编写任务执行的业务逻辑。
    • Job实例在Quartz中的生命周期:每次调度器执行Job时,它在调用execute方法前会创建一个新的 Job 实例,当调用完成后,关联的Job对象实例会被释放,释放的实例会被垃圾回收机制回收。
    • JobDetail:JobDetail为Job实例提供了许多设置属性,以及JobDataMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。
    • JobDetail重要属性:namegroupjobClassJobDataMap
    JobDetail job = JobBuilder.newJob(HelloJob.class)
            .withIdentity("job1", "group1") // 定义该实例唯一标识,并指定一个组
            .build();
    
    System.out.println("name:" +job.getKey().getName());
    System.out.println("group:" +job.getKey().getGroup());
    System.out.println("jobClass:" +job.getJobClass().getName());
    

    4、JobExecutionContext

    • 当 Scheduler 调用一个 Job,就会将 JobExecutionContext 传递给 Job 的 execute() 方法;
    • Job 能通过 JobExecutionContext 对象访问到 Quartz 运行时候的环境以及 Job 本身的明细数据。
    public class HelloJob implements Job {
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    
            Trigger trigger = jobExecutionContext.getTrigger(); //获取Trigger
            JobDetail jobDetail = jobExecutionContext.getJobDetail(); //获取JobDetail
            Scheduler scheduler = jobExecutionContext.getScheduler(); //获取Scheduler
    
            trigger.getKey().getName(); //获取Trigger名字
            trigger.getKey().getGroup(); //获取Trigger组名(默认为 DEFAULT)
    
            jobExecutionContext.getScheduledFireTime(); //触发器触发的预定时间。
            jobExecutionContext.getFireTime(); //实际触发时间。例如,计划时间可能是 10:00:00,但如果调度程序太忙,实际触发时间可能是 10:00:03。
            jobExecutionContext.getPreviousFireTime(); //上次触发时间
            jobExecutionContext.getNextFireTime(); //下次触发时间
    
            System.out.println(new Date());
        }
    }
    

    5、JobDataMap介绍

    (1)使用Map获取

    • 在进行任务调度时,JobDataMap 存储在 JobExecutionContext 中,非常方便获取。
    • JobDataMap 可以用来装载任何可序列化的数据对象,当 Job 实例对象被执行时这些参数对象会传递给它。
    • JobDataMap 实现了 JDK 的 Map 接口,并且添加了非常方便的方法用来存取基本数据类型。

    在定义 Trigger 或者 JobDetail 时,将 JobDataMap 传入,然后 Job 中便可以获取到 JobDataMap 中的参数

    public class HelloScheduler {
        public static void main(String[] args) throws SchedulerException {
            //1. 调度器(Scheduler)
            Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
    		
    		JobDataMap jobDataMap2 = new JobDataMap();
            jobDataMap2.put("message", "JobDetailMessage");
    
            //2. 任务实例(JobDetail)
            JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("job1", "jobGroup1")
                    .usingJobData(jobDataMap2)
                    .build();
    
    		// 定义 JobDataMap
            JobDataMap jobDataMap = new JobDataMap();
            jobDataMap.put("message", "TriggerMessage");
    
            //3. 触发器(Trigger)
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "triggerGroup1")
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
                    .endAt(new Date(new Date().getTime() + 3000L))
                    .usingJobData(jobDataMap) // 将 JobDataMap 放入 Trigger 中
                    .build();
    
            defaultScheduler.scheduleJob(jobDetail, trigger);
            defaultScheduler.start();
        }
    }
    

    HelloJob.java

    public class HelloJob implements Job {
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        	System.out.println(jobExecutionContext.getTrigger().getJobDataMap().get("message")); //TriggerMessage
            System.out.println(jobExecutionContext.getJobDetail().getJobDataMap().get("message")); //JobDetailMessage
    
            System.out.println(jobExecutionContext.getMergedJobDataMap().get("message")); //TriggerMessage
            System.out.println(new Date());
        }
    }
    

    (2)使用 Setter 方法获取

    Job实现类中添加setter方法对应JobDataMap的键值,Quartz框架默认的JobFactory实现类在初始化Job实例对象时会自动调用这些setter方法。

    HelloScheduler 类和上面一样。

    HelloJob.java:

    @Data
    public class HelloJob implements Job {
    
        private String message;
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            System.out.println(message); //TriggerMessage
            System.out.println(new Date());
        }
    }
    

    注意:如果遇到同名的 keyTriggerJobDataMap 的值会覆盖 JobDetailJobDataMap 同名的 Key

    6、有状态的Job和无状态的Job(@PersistJobDataAfterExecution)

    有状态的 Job 可以理解为多次 Job 调用期间可以持有一些状态信息,这些状态信息存储在 JobDataMap 中,而默认的无状态 Job 每次调用时都会创建一个新的 JobDataMap

    (1)修改HelloSchedulerDemo.java。在 JobDetail 中添加 .usingJobData("count", 0),表示计数器。

    JobDetail job = JobBuilder.newJob(HelloJob.class)
            .withIdentity("job1", "group1") // 定义该实例唯一标识,并指定一个组
            .usingJobData("message", "打印日志")
            .usingJobData("count", 0)
            .build();
    

    (2)HelloJob.java

    @Data
    @PersistJobDataAfterExecution
    public class HelloJob implements Job {
    
        private Integer count;
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            System.out.println(++count);
            jobExecutionContext.getJobDetail().getJobDataMap().put("count", count);
        }
    }
    

    HelloJob类没有添加 @PersistJobDataAfterExecution 注解,每次调用时都会创建一个新的 JobDataMap。不会累加。

    HelloJob类添加 @PersistJobDataAfterExecution 注解,多次调用期间可以持有一些状态信息,即可以实现 count 的累加。

    7、Trigger

    在这里插入图片描述

    (1)SimpleTrigger触发器

    SimpleTrigger 对于设置和使用是最为简单的一种QuartzTrigger。

    它是为那种需要在特定的日期/时间启动,且以一个可能的间隔时间重复执行 n 次的 Job 所设计的。

    案例一:表示在一个指定的时间段内,执行一次作业任务;

    SimpleTrigger 常用方法:

    方法说明
    startNow()Scheduler 开始执行时,触发器也即执行
    startAt(new Date())在指定的时间开始执行
    withIntervalInSeconds(2)执行间隔,方法名中对应时间单位
    repeatForever()一直重复执行
    withRepeatCount(3)重复执行指定的次数
    endAt(new Date())结束时间

    例子:

    //立即开始执行,2秒执行一次,重复3次,3秒后结束执行(当重复次数或者结束时间有一个先达到时,就会停止执行)
    Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "triggerGroup1")
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).withRepeatCount(3))
                    .endAt(new Date(new Date().getTime() + 3000L))
                    .build();
    

    需要注意的点

    • SimpleTrigger的属性有:开始时间、结束时间、重复次数和重复的时间间隔。
    • 重复次数属性的值可以为0、正整数、或常量 SimpleTrigger.REPEAT_INDEFINITELY。
    • 重复的时间间隔属性值必须为大于0或者长整形的正整数,以毫秒作为时间单位,当重复的时间间隔为0时,意味着与Trigger同时触发执行。
    • 如果有指定结束时间属性值,则结束时间属性优先于重复次数属性,这样的好处在于:当我们需要创建一个每间隔10秒触发一次直到指定的结束时间的Trigger,而无需去计算从开始到结束的所重复的次数,我们只需简单的指定结束时间和使用REPEAT_INDEFINITELY作为重复次数的属性值即可。

    (2)CronTrigger触发器

    如果你需要像日历那样按日程来触发任务,而不是像 SimpleTrigger 那样每隔特定的间隔时间触发,CronTrigger 通常比 SimpleTrigger 更有用,因为它是基于日历的作业调度器。

    案例:

    Trigger trigger = TriggerBuilder.newTrigger()
            .withIdentity("trigger1", "group1")
            .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))  // 日历
            .build();
    

    8、 SchedulerFactory

    Quartz以模块方式架构,因此,要使它运行,几个组件必须很好的咬合在一起。幸运的是,已经有了一些现存的助手可以完成这些工作。

    所有的Scheduler实例由SchedulerFactory创建。

    Quartz的三个核心概念:调度器、任务、触发器,三者之间的关系是:

    在这里插入图片描述

    大家都知道,一个作业,比较重要的三个要素就是Scheduler,JobDetail,Trigger;而Trigger对于Job而言就好比一个驱动器,没有触发器来定时驱动作业,作业就无法运行;对于Job而言,一个Job可以对应多个Trigger,但对于Trigger而言,一个Trigger只能对应一个Job,所以一个Trigger只能被指派给一个Job;如果你需要一个更负责的触发计划,你可以创建多个Trigger并指派它们给同一个Job。

    (1)StdSchedulerFactory

    Quartz 默认的 SchedulerFactory

    • 使用一组参数(java.util.Properties)来创建和初始化Quartz调度器
    • 配置参数一般存储在 quartz.properties 文件中
    • 调用 getScheduler方法就能创建和初始化调度器对象

    创建方法:

    //静态方法
    Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
    
    //实例方法
    StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
    Scheduler scheduler = stdSchedulerFactory.getScheduler();
    

    (2)DirectSchedulerFactory(了解)

    DirectSchedulerFactory 是对 SchedulerFactory 的直接实现,通过它可以直接构建 Scheduler、ThreadPool 等

    DirectSchedulerFactory directSchedulerFactory = DirectSchedulerFactory.getInstance();
    Scheduler scheduler = directSchedulerFactory.getScheduler();
    

    9、Scheduler 常用方法

    scheduler.scheduleJob(jobDetail, trigger); //绑定jobDetail与trigger
    scheduler.checkExists(JobKey.jobKey(name, group))	//检查JobDetail是否存在
    scheduler.checkExists(TriggerKey.triggerKey(name, group))	//检查Trigger是否存在
    scheduler.deleteJob(JobKey.jobKey(name, group))		//删除jobDetail
    
    scheduler.triggerJob(JobKey.jobKey(name, group), dataMap)	//立即执行一次指定的任务
    
    scheduler.start();		//启动任务调度
    scheduler.pauseJob(jobKey);	//暂停指定的job
    scheduler.standby();	//任务调度挂起,即暂停操作
    scheduler.shutdown();	//关闭任务调度,同shutdown(false)
    scheduler.shutdown(true);	//表示等待所有正在执行的Job执行完毕之后,再关闭Scheduler
    scheduler.shutdown(false);	// 表示直接关闭Scheduler
    

    10、 Quartz.properties

    默认路径:quartz-2.3.2 中的 org.quartz.quartz.properties

    我们也可以在项目的资源下添加 quartz.properties 文件,去覆盖底层的配置文件。

    #===============================================================
    #Configure Main Scheduler Properties 调度器属性
    #===============================================================
    #调度器的实例名
    org.quartz.scheduler.instanceName = QuartzScheduler
    #调度器的实例ID,大多数情况设置为AUTO即可
    org.quartz.scheduler.instanceId = AUTO
    
    #===============================================================
    #Configure ThreadPool 线程池属性
    #===============================================================
    #处理Job的线程个数,至少为1,但最多的话最好不要超过100,在多数机器上设置该值超过100的话显得相当不实用了,特别是在你的Job执行时间较长的情况下
    org.quartz.threadPool.threadCount =  5
    #线程的优先级,优先级别搞的线程比优先级别低的线程优先得到执行。最小为1,最大为10,默认为5
    org.quartz.threadPool.threadPriority = 5
    #一个实现了org.quartz.spi.threadPool接口的类,Quartz自带的线程池实现类是org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
    
    #===============================================================
    #Configure JobStore 作业存储设置
    #===============================================================
    #Job默认是存储在内存中的,即下面配置
    org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
    
    #===============================================================
    #Configure Plugins 插件配置
    #===============================================================
    org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin
    
    org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
    org.quartz.plugin.jobInitializer.failOnFileNotFound = true
    org.quartz.plugin.jobInitializer.validating=false
    

    也可以编写程序代码操作quartz.properties文件的内容:

    // 创建工厂实例
    StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
    // 创建配置工厂的属性的对象
    Properties prop = new Properties();
    prop.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool");
    prop.put("org.quartz.threadPool.threadCount", "5");
    
    try {
        // 加载上面定义的属性
        schedulerFactory.initialize(prop);
    
        Scheduler scheduler = schedulerFactory.getScheduler();
    
        scheduler.start();
    } catch (SchedulerException e) {
        e.printStackTrace();
    }
    

    三、Quartz监听器

    1、概念

    Quartz的监听器用于当任务调度中你所关注事件发生时,能够及时获取这一事件的通知。类似于任务执行过程中的邮件、短信类的提醒。Quartz监听器主要由JobListener、TriggerListener、SchedulerListener三种,顾名思义,分布表示任务、触发器、调度器对应的监听器。三者的使用方法类似,在开始介绍三种监听器之前,需要明确两个概念:全局监听器与非全局监听器,二者的区别在于:

    • 全局监听器能够接收到所有的Job/Trigger的事件通知
    • 而非全局监听器只能接收到在其上注册的Job或者Trigger的事件,不在其上注册的Job或Trigger则不会进行监听。

    本课程关于全局与非全局的监听器的使用,将一一介绍。

    2、JobListener

    任务调度过程中,与任务Job相关的事件包括:Job开始要执行的提示;Job执行完成的提示等。

    public interface JobListener {
        public String getName();
        public void jobToBeExecuted(JobExecutionContext context);
        public void jobExecutionVetoed(JobExecutionContext context);
        public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException);
    }
    

    其中:

    . getName方法:用于获取该JobListener的名称。
    . jobToBeExecuted方法:Scheduler在JobDetail将要被执行时调用这个方法。
    . jobExecutionVetoed方法:Scheduler在JobDetail即将被执行,但又被TriggerListener否决时会调用该方法。
    . jobWasExecuted方法:Scheduler在JobDetail被执行之后调用这个方法。

    示例:

    HelloJobListener.java

    // 定义任务类
    public class HelloJobListener implements Job {
    
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            // 输出当前时间
            Date date = new Date();
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String dateString = dateFormat.format(date);
            // 工作内容
            System.out.println("正在进行数据库的备份工作,备份数据库的时间是:" +dateString);
        }
    }
    

    创建自定义的JobListener

    MyJobListener.java

    public class MyJobListener implements JobListener {
    
        @Override
        public String getName() {
            String name = this.getClass().getSimpleName();
            System.out.println("监听器的名称是:" +name);
            return name;
        }
    
        @Override
        public void jobToBeExecuted(JobExecutionContext context) {
            String name = context.getJobDetail().getKey().getName();
            System.out.println("Job的名称是:" +name + "          Scheduler在JobDetail将要被执行时调用的方法");
        }
    
        @Override
        public void jobExecutionVetoed(JobExecutionContext context) {
            String name = context.getJobDetail().getKey().getName();
            System.out.println("Job的名称是:" +name + "          Scheduler在JobDetail即将被执行,但又被TriggerListener否决时会调用该方法");
        }
    
        @Override
        public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
            String name = context.getJobDetail().getKey().getName();
            System.out.println("Job的名称是:" +name + "          Scheduler在JobDetail被执行之后调用这个方法");
        }
    
    }
    

    执行调度器

    HelloSchedulerDemoJobListener.java

    public class HelloSchedulerDemoJobListener {
    
        public static void main(String[] args) throws Exception {
            // 1、调度器(Scheduler),从工厂中获取调度的实例(默认:实例化new StdSchedulerFactory();)
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    
            // 2、任务实例(JobDetail)定义一个任务调度实例,将该实例与HelloJobSimpleTrigger绑定,任务类需要实现Job接口
            JobDetail jobDetail = JobBuilder.newJob(HelloJobListener.class) // 加载任务类,与HelloJob完成绑定,要求HelloJob实现Job接口
                    .withIdentity("job1", "group1") // 参数1:任务的名称(唯一实例);参数2:任务组的名称
                    .build();
    
            // 3、触发器(Trigger)定义触发器,马上执行,然后每5秒重复执行一次
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1") // 参数1:触发器的名称(唯一实例);参数2:触发器组的名称
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5).withRepeatCount(2))  // 每5秒执行一次,连续执行3次后停止,默认是0
                    .build();
            // 4、让调度器关联任务和触发器,保证按照触发器定义的调整执行任务
            scheduler.scheduleJob(jobDetail, trigger);
            
            // 创建并注册一个全局的Job Listener
            // scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());
            // 创建并注册一个局部的Job Listener,表示指定的任务Job
            scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("job1", "group1")));
    
            // 5、启动
            scheduler.start();
            // 关闭
            //scheduler.shutdown();
        }
    
    }
    

    3、TriggerListener

    任务调度过程中,与触发器Trigger相关的事件包括:触发器触发、触发器未正确触发、触发器完成等。

    public interface TriggerListener {
        public String getName();
        public void triggerFired(Trigger trigger, JobExecutionContext context);
        public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
        public void triggerMisfired(Trigger trigger);
        public void triggerComplete(Trigger trigger, JobExecutionContext context,            CompletedExecutionInstruction triggerInstructionCode)
    }
    

    其中:

    . getName方法:用于获取触发器的名称。
    . triggerFired方法:当与监听器关联的Trigger被触发,Job上的Execute()方法将被执行时,Scheduler就调用该方法。
    . vetoJobExecution方法:在Trigger触发后,Job将要执行时由Scheduler调用这个方法。TriggerListener给了一个选择去否决Job的执行。假如这个方法返回true,这个Job将不会为此次Trigger触发而得到执行。
    . triggerMisfired方法:Scheduler调用这个方法是在Trigger错过触发时。你应该关注此方法中持续时间长的逻辑:在出现许多错过触发的Trigger时,长逻辑会导致骨牌效应。你应当保持这个方法尽量的小。
    . triggerComplete方法:Trigger被触发并且完成了Job的执行时,Scheduler调用这个方法。

    示例:

    下面的例子简单展示了TriggerListener的使用,其中创建并注册TriggerListener与JobListener几乎类似。

    HelloJobListener.java

    // 定义任务类
    public class HelloJobListener implements Job {
    
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            // 输出当前时间
            Date date = new Date();
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String dateString = dateFormat.format(date);
            // 工作内容
            System.out.println("正在进行数据库的备份工作,备份数据库的时间是:" +dateString);
        }
    }
    

    MyTriggerListener.java

    public class MyTriggerListener implements TriggerListener {
        
        private String name;
        // 构造方法,自定义传递触发器的名称,默认是类的名称
        public MyTriggerListener(String name) {
            super();
            this.name = name;
        }
        @Override
        public String getName() {
            return this.name;  // 不返还会抛出一个名称为空的异常
        }
    
    //    @Override
    //    public String getName() {
    //        String name = this.getClass().getSimpleName();
    //        System.out.println("触发器的名称:" +name);
    //        return name;  // 不返还会抛出一个名称为空的异常
    //    }
    
        @Override
        public void triggerFired(Trigger trigger, JobExecutionContext context) {
            String name = this.getClass().getSimpleName();
            System.out.println(name +"被触发");
        }
    
        @Override
        public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
            String name = this.getClass().getSimpleName();
            // TriggerListener给了一个选择去否决Job的执行。假如这个方法返回true,这个Job将不会为此次Trigger触发而得到执行。
            System.out.println(name +" 没有被触发");
            return false;  // true:表示不会执行Job的方法
        }
    
        @Override
        public void triggerMisfired(Trigger trigger) {
            String name = this.getClass().getSimpleName();
            // Scheduler调用这个方法是在Trigger错过触发时
            System.out.println(name +" 错过触发");
        }
    
        @Override
        public void triggerComplete(Trigger trigger, JobExecutionContext context,
                CompletedExecutionInstruction triggerInstructionCode) {
            String name = this.getClass().getSimpleName();
            // Trigger被触发并且完成了Job的执行时,Scheduler调用这个方法。
            System.out.println(name +" 完成之后触发");
        }
    
    }
    

    HelloSchedulerDemoTriggerListener.java

    public class HelloSchedulerDemoTriggerListener {
    
        public static void main(String[] args) throws Exception {
            // 1、调度器(Scheduler),从工厂中获取调度的实例(默认:实例化new StdSchedulerFactory();)
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    
            // 2、任务实例(JobDetail)定义一个任务调度实例,将该实例与HelloJobSimpleTrigger绑定,任务类需要实现Job接口
            JobDetail jobDetail = JobBuilder.newJob(HelloJobListener.class) // 加载任务类,与HelloJob完成绑定,要求HelloJob实现Job接口
                    .withIdentity("job1", "group1") // 参数1:任务的名称(唯一实例);参数2:任务组的名称
                    .build();
    
            // 3、触发器(Trigger)定义触发器,马上执行,然后每5秒重复执行一次
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1") // 参数1:触发器的名称(唯一实例);参数2:触发器组的名称
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5).withRepeatCount(2))  // 每5秒执行一次,连续执行3次后停止,默认是0
                    .build();
            // 4、让调度器关联任务和触发器,保证按照触发器定义的调整执行任务
            scheduler.scheduleJob(jobDetail, trigger);
    
            // 创建并注册一个全局的Trigger Listener
            // scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), EverythingMatcher.allTriggers());
            // 创建并注册一个局部的Trigger Listener
            scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "group1")));
    
            // 5、启动
            scheduler.start();
            // 关闭
            //scheduler.shutdown();
        }
    
    }
    

    4、SchedulerListener

    SchedulerListener会在Scheduler的生命周期中关键事件发生时被调用。与Scheduler有关的事件包括:增加一个Job/Trigger,删除一个Job/Trigger,Scheduler发生严重错误,关闭Scheduler等。

    public interface SchedulerListener {
        public void jobScheduled(Trigger trigger);
        public void jobUnscheduled(TriggerKey triggerKey);
        public void triggerFinalized(Trigger trigger);
        public void triggersPaused(String triggerGroup);
        public void triggersResumed(String triggerGroup);
        public void jobsPaused(String jobGroup);
        public void jobsResumed(String jobGroup);
        public void schedulerError(String msg, SchedulerException cause);
        public void schedulerStarted();
        public void schedulerInStandbyMode();
        public void schedulerShutdown();
        public void schedulingDataCleared()
    }
    

    其中:

    . jobScheduled方法:用于部署JobDetail时调用。
    . jobUnscheduled方法:用于卸载JobDetail时调用。
    . triggerFinalized方法:当一个Trigger来到了再也不会触发的状态时调用这个方法。除非这个Job已设置成了持久性,否则它就会从Scheduler中移除。
    . triggersPaused方法:Scheduler调用这个方法是发生在一个Trigger或Trigger组被暂停时。假如是Trigger组的话,triggerName参数将为null。
    . triggersResumed方法:Scheduler调用这个方法是发生在一个Trigger或Trigger组从暂停中恢复时。假如是Trigger组的话,triggerName参数将为null。
    . jobsPaused方法:当一个或一组JobDetail暂停时调用这个方法。
    . jobsResumed方法:当一个或一组Job从暂停上恢复时调用这个方法。假如是一个Job组,jobName将为null。
    . schedulerError方法:在Scheduler的正常运行期间产生一个严重错误时调用这个方法。
    . schedulerStarted方法:当Scheduler开启时,调用该方法。
    . schedulerInStandbyMode方法:当Scheduler处于StandBy模式时,调用该方法。
    . schedulerShutdown方法:当Scheduler停止时,调用该方法。
    . schedulingDataCleared方法:当Scheduler中的数据被清除时,调用该方法。

    示例:

    下面的代码简单描述了如何使用SchedulerListener方法:

    HelloJobListener.java

    // 定义任务类
    public class HelloJobListener implements Job {
    
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            // 输出当前时间
            Date date = new Date();
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String dateString = dateFormat.format(date);
            // 工作内容
            System.out.println("正在进行数据库的备份工作,备份数据库的时间是:" +dateString);
        }
    }
    

    MySchedulerListener.java

    public class MySchedulerListener implements SchedulerListener {
    
        @Override
        public void jobScheduled(Trigger trigger) {
            String name = trigger.getKey().getName();
            // 用于部署JobDetail时调用
            System.out.println(name +" 完成部署");
        }
    
        @Override
        public void jobUnscheduled(TriggerKey triggerKey) {
            String name = triggerKey.getName();
            // 用于卸载JobDetail时调用
            System.out.println(name +" 完成卸载");
        }
    
        @Override
        public void triggerFinalized(Trigger trigger) {
            String name = trigger.getKey().getName();
            // 当一个Trigger来到了再也不会触发的状态时调用这个方法。除非这个Job已设置成了持久性,否则它就会从Scheduler中移除。
            System.out.println(name +" 触发器被移除");
        }
    
        @Override
        public void triggerPaused(TriggerKey triggerKey) {
            String name = triggerKey.getName();
            // Scheduler调用这个方法是发生在一个Trigger或Trigger组被暂停时。假如是Trigger组的话,triggerName参数将为null。
            System.out.println(name +" 正在被暂停");
        }
    
        @Override
        public void triggersPaused(String triggerGroup) {
            // Scheduler调用这个方法是发生在一个Trigger或Trigger组被暂停时。假如是Trigger组的话,triggerName参数将为null。
            System.out.println("触发器组" +triggerGroup +" 正在被暂停");
        }
    
        @Override
        public void triggerResumed(TriggerKey triggerKey) {
            // Scheduler调用这个方法是发生在一个Trigger或Trigger组从暂停中恢复时。假如是Trigger组的话,triggerName参数将为null。参数将为null。
            String name = triggerKey.getName();
            System.out.println(name +" 正在从暂停中恢复");
        }
    
        @Override
        public void triggersResumed(String triggerGroup) {
            // Scheduler调用这个方法是发生在一个Trigger或Trigger组从暂停中恢复时。假如是Trigger组的话,triggerName参数将为null。参数将为null。
            System.out.println("触发器组" +triggerGroup +" 正在从暂停中恢复");
        }
    
        @Override
        public void jobAdded(JobDetail jobDetail) {
            // 
            System.out.println(jobDetail.getKey() +" 添加工作任务");
        }
    
        @Override
        public void jobDeleted(JobKey jobKey) {
            // 
            System.out.println(jobKey +" 删除工作任务");
        }
    
        @Override
        public void jobPaused(JobKey jobKey) {
            // 
            System.out.println(jobKey +" 工作任务正在被暂停");
        }
    
        @Override
        public void jobsPaused(String jobGroup) {
            // 
            System.out.println("工作组" +jobGroup +" 正在被暂停");
        }
    
        @Override
        public void jobResumed(JobKey jobKey) {
            // 
            System.out.println(jobKey +" 正在从暂停中恢复");
        }
    
        @Override
        public void jobsResumed(String jobGroup) {
            // 
            System.out.println("工作组" +jobGroup +" 正在从暂停中恢复");
        }
    
        @Override
        public void schedulerError(String msg, SchedulerException cause) {
            // 在Scheduler的正常运行期间产生一个严重错误时调用这个方法。
            System.out.println("产生严重错误的时候调用" +msg +"    " +cause.getUnderlyingException());
        }
    
        @Override
        public void schedulerInStandbyMode() {
            // 当Scheduler处于StandBy模式时,调用该方法。
            System.out.println("调度器被挂起模式的时候调用");
        }
    
        @Override
        public void schedulerStarted() {
            // 当Scheduler开启时,调用该方法
            System.out.println("调度器开启的时候调用");
        }
    
        @Override
        public void schedulerStarting() {
            // 
            System.out.println("调度器正在开启的时候调用");
        }
    
        @Override
        public void schedulerShutdown() {
            // 
            System.out.println("调度器关闭的时候调用");
        }
    
        @Override
        public void schedulerShuttingdown() {
            // 
            System.out.println("调度器正在关闭的时候调用");
        }
    
        @Override
        public void schedulingDataCleared() {
            // 当Scheduler中的数据被清除时,调用该方法
            System.out.println("调度器数据被清除的时候调用");
        }
    
    }
    

    HelloSchedulerDemoTriggerListener.java

    public class HelloSchedulerDemoTriggerListener {
    
        public static void main(String[] args) throws Exception {
            // 1、调度器(Scheduler),从工厂中获取调度的实例(默认:实例化new StdSchedulerFactory();)
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    
            // 2、任务实例(JobDetail)定义一个任务调度实例,将该实例与HelloJobSimpleTrigger绑定,任务类需要实现Job接口
            JobDetail jobDetail = JobBuilder.newJob(HelloJobListener.class) // 加载任务类,与HelloJob完成绑定,要求HelloJob实现Job接口
                    .withIdentity("job1", "group1") // 参数1:任务的名称(唯一实例);参数2:任务组的名称
                    .build();
    
            // 3、触发器(Trigger)定义触发器,马上执行,然后每5秒重复执行一次
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1") // 参数1:触发器的名称(唯一实例);参数2:触发器组的名称
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5).withRepeatCount(2))  // 每5秒执行一次,连续执行3次后停止,默认是0
                    .build();
            // 4、让调度器关联任务和触发器,保证按照触发器定义的调整执行任务
            scheduler.scheduleJob(jobDetail, trigger);
    
            // 创建调度器的监听
            scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());
            // 移除对应的调度器的监听
            // scheduler.getListenerManager().removeSchedulerListener(new MySchedulerListener());
    
            // 5、启动
            scheduler.start();
    
            // 线程延迟7秒后关闭
            Thread.sleep(7000L);
    
            // 关闭
            scheduler.shutdown();
        }
    
    }
    

    四、持久化到Mysql中

    1. 下载sql文件

    Quartz 原码中有 sql 文件:

    下载然后导入到数据库中,我这里使用的是 mysql5.7

    表名描述
    QRTZ_BLOB_TRIGGERS作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候)
    QRTZ_CALENDARS以 Blob 类型存储 Quartz 的 Calendar 信息
    QRTZ_CRON_TRIGGERS存储 Cron Trigger,包括 Cron 表达式和时区信息
    QRTZ_FIRED_TRIGGERS存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息
    QRTZ_JOB_DETAILS存储每一个已配置的 Job 的详细信息
    QRTZ_LOCKS存储程序的非观锁的信息(假如使用了悲观锁)
    QRTZ_PAUSED_TRIGGER_GRPS存储已暂停的 Trigger 组的信息
    QRTZ_SCHEDULER_STATE存储少量的有关 Scheduler 的状态信息,和别的 Scheduler 实例(假如是用于一个集群中)
    QRTZ_SIMPLE_TRIGGERS存储简单的 Trigger,包括重复次数,间隔,以及已触的次数
    QRTZ_SIMPROP_TRIGGERS
    QRTZ_TRIGGERS存储已配置的 Trigger 的信息

    2. 引入依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zyx</groupId>
    <artifactId>quartz</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <exclusions>
                <exclusion>
                	<!--排除自带的JDBC连接池-->
                    <groupId>com.mchange</groupId>
                    <artifactId>c3p0</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    
    	<!--定时任务需要依赖context模块-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
    
        <!--连接数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--下面的包可以替换为 mybatis 或者 mybatisPlus-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
    </dependencies>
    

    3. 配置SchedulerFactory

    配置数据源:

    spring:
      datasource:
        username: root
        password: root
        url: jdbc:mysql://10.211.55.12:3306/test
        driver-class-name: com.mysql.cj.jdbc.Driver
    

    配置 SchedulerFactory:

    @Configuration
    public class ScheduleConfig {
    
        @Bean
        public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)
        {
            SchedulerFactoryBean factory = new SchedulerFactoryBean();
            factory.setDataSource(dataSource);
    
            // quartz参数
            Properties prop = new Properties();
            prop.put("org.quartz.scheduler.instanceName", "ZyxScheduler");
            prop.put("org.quartz.scheduler.instanceId", "AUTO"); //如果使用集群,instanceId必须唯一,设置成AUTO
            // 线程池配置
            prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
            prop.put("org.quartz.threadPool.threadCount", "20"); //线程数
            prop.put("org.quartz.threadPool.threadPriority", "5"); //优先级
            // JobStore配置
            prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX"); //配置使用数据库
            // 集群配置
            prop.put("org.quartz.jobStore.isClustered", "true"); //是否是集群模式
            prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
            prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
            prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
    
            // sqlserver 启用
            // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
            prop.put("org.quartz.jobStore.misfireThreshold", "12000");
            prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); //数据库表前缀
            factory.setQuartzProperties(prop);
    
            factory.setSchedulerName("ZyxScheduler");
            // 延时启动
            factory.setStartupDelay(1);
            factory.setApplicationContextSchedulerContextKey("applicationContextKey");
            // 可选,QuartzScheduler
            // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
            factory.setOverwriteExistingJobs(true);
            // 设置自动启动,默认为true
            factory.setAutoStartup(true);
    
            return factory;
        }
    }
    

    4. 使用自定义的Scheduler

    定义一个简单的 Job:

    @Data
    public class HelloJob implements Job {
        
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            System.out.println(new Date());
        }
    }
    

    使用自定义的 Scheduler:

    @SpringBootTest
    class QuartzApplicationTests {
    
    	//将上面配置好的 factoryBean 注入进来
        @Autowired
        private SchedulerFactoryBean factoryBean;
    
        @Test
        void contextLoads() throws SchedulerException, InterruptedException {
            Scheduler scheduler = factoryBean.getScheduler();
            scheduler.clear();
    
            //2. 任务实例(JobDetail)
            JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("job1", "jobGroup1")
                    .build();
    
            //3. 触发器(Trigger)
            Trigger trigger = TriggerBuilder.newTrigger()
                    .startNow()
                    .withIdentity("trigger1", "triggerGroup1")
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
                    .build();
    
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
            Thread.sleep(100000);
        }
    }
    

    5. 查看数据库

    查看数据库,可以发现 Quartz 中相关的数据已经保存到数据库中了
    在这里插入图片描述
    在这里插入图片描述

    6. 再次启动 Scheduler

    直接让程序运行起来,不创建新的定时任务,会发现刚才保存到数据库中的定时任务会自动执行

    @SpringBootTest
    class QuartzApplicationTests {
    
        @Autowired
        private SchedulerFactoryBean factoryBean;
    
        @Test
        void contextLoads() throws SchedulerException, InterruptedException {
            Thread.sleep(100000);
        }
    
    }
    
    展开全文
  • Quartz定时任务

    千次阅读 2019-01-27 17:25:35
    一、Quartz的核心概念 1.任务job job就是想要实现的任务类,每一个job必须实现job接口,且实现接口中的 excute()方法。 2.触发器Trigger Trigger为你执行任务的触发器,可以设置特定时间执行该任务 Trigger主要...

    一、Quartz的核心概念

    1.任务job

    job就是想要实现的任务类,每一个job必须实现job接口,且实现接口中的 excute()方法。

    2.触发器Trigger

    Trigger为你执行任务的触发器,可以设置特定时间执行该任务
    Trigger主要包含SimpleTrigger和CronTrigger两种

    3.调度器Scheduler

    Scheduler为任务的调度器,它会将任务job及触发器Trigger整合起来,负责基于Trigger设定的时间来执行job

    4.Quartz的体系结构

    在这里插入图片描述

    5.Quartz的核心组件

    在这里插入图片描述

    二、Quartz的基本功能

    pom.xml文件中添加quartz相关jar包的坐标

    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.0</version>
    </dependency>
    
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz-jobs</artifactId>
        <version>2.2.1</version>
    </dependency>
    

    1.首先创建一个Myjob工作类并实现Job接口,并重写里面的execute方法,为了直观的观察定时任务,我们在里面输出当前时间

    /**
     * Created by yan on 2019/1/27.
     */
    public class MyJob implements Job {
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            System.out.print("quartz:I coming--------");
            System.out.println((new SimpleDateFormat("yyyy-MM-dd HH-mm-ss")).format(new Date()));
        }
    }
    

    2.创建一个MyScheduler

    /**
     * Created by yan on 2019/1/27.
     */
    public class MyScheduler {
        public static void main(String[] args) {
            //创建一个JobDetail实例
            JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                    .withIdentity("jobDetail","group1")//jobdetail的唯一标示
                    .build();
    
            //创建一个Trigger实例
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger","group1")//Trigger的唯一标识
                    .startNow()    //简单的定时器                          每2秒钟执行一次            执行到永远
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
                    .build();
    
            //创建Scheduler实例
            //通过SchedulerFactory创建
            SchedulerFactory schedulerFactory = new StdSchedulerFactory();
            try {
                Scheduler scheduler = schedulerFactory.getScheduler();
                //使用scheduler将jobDetail和trigger结合起来
                scheduler.scheduleJob(jobDetail,trigger);
                //开始
                scheduler.start();
    
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
    
        }
    }
    

    OutPut:
    在这里插入图片描述

    Scheduler实例创建的两种方式

    //通过SchedulerFactory创建
    SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    scheduler = schedulerFactory.getScheduler();
    
    //通过StdSchedulerFactory创建
    Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();//类名.方法(static)
    

    JobDataMap 介绍

    JobDataMap介绍
    1).

    • 在进行任务调度时,JobDataMap存储在JobExecutionContext中 ,非常方便获取。
    • JobDataMap可以用来装载任何可序列化的数据对象,当job实例对象被执行时这些参数对象会传递给它。
    • JobDataMap实现了JDK的Map接口,并且添加了非常方便的方法用来存取基本数据类型。
      我们从底层代码中查看JobDataMap Implement Map
      在这里插入图片描述在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      JobDatamap Implement Map 确认无误
      2).
      Job实现类中添加setter方法对应JobDataMap的键值,Quartz框架默认的JobFactory实现类在初始化job实例对象时会
      自动地调用这些setter方法。
      3).
      如果遇到同名的key,Trigger中的.usingJobData(“message”, “新年快乐”)
      会覆盖
      JobDetail的.usingJobData(“message”, “新年不快乐”)。
    //jobDetail中添加JobDataMap
    .usingJobData("message","祝大家新年快乐")
    //trigger中添加JobDataMap
    .usingJobData("message","祝大家新年长胖20斤")
    
    //MyJob实现类中 private message ,并给set方法
    private String message;
    
        public void setMessage(String message) {
            this.message = message;
        }
    
    System.out.println(message);
    

    运行OutPut:
    在这里插入图片描述

    有状态Job和无状态Job

    有状态的Job可以理解为多次Job调用期间可以持有一些状态信息,这些状态信息存储在JobDataMap中,
    而默认的无状态job每次调用时都会创建一个新的JobDataMap。

    //jobDetail中添加JobDataMap
    .usingJobData("count",1)
    
    //MyJob中private count 并给set方法
    private Integer count;
    
        public void setCount(Integer count) {
            this.count = count;
        }
    
    count++;
    System.out.println(count);
    context.getJobDetail().getJobDataMap().put("count",count);
    

    OutPut:
    在这里插入图片描述
    默认给定无状态的Job,可以通过注解的方式改变Job的状态为有状态的Job

    //在MyJob类上添加
    @PersistJobDataAfterExecution		//可以将无状态的job转变为有状态的job
    

    在这里插入图片描述

    Trigger触发器

    上面的代码我们使用的是SimpleTrigger(简单的触发器),这种触发器只能完成一定频率的触发任务(即:每隔多长时间触发),这显然不能满足我们对定时任务的需求,因此CronTrigger便横空出世

    如果你需要像日历那样按日程来触发任务,而不是像SimpleTrigger 那样每隔特定的间隔时间触发,CronTriggers通常比SimpleTrigger更有用,因为它是基于日历的作业调度器。

    使用CronTrigger,你可以指定诸如“每隔周五的晚上8:00”,或者“每个工作日的9:30”或者“从每个周一、周三、周五的上午9:00到上午10:00之间每隔五分钟”这样日程安排来触发。甚至,象SimpleTrigger一样,CronTrigger也有一个startTime以指定日程从什么时候开始,也有一个(可选的)endTime以指定何时日程不再继续。
    在这里插入图片描述
    当然这些表达式是不需要死记硬背的,我们可以通过网上的Cron表达式生成器进行转换
    Cron表达式转换
    在这里插入图片描述

    //SimpleTrigger的创建
    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()
    
    //CronTrigger的创建
    .withSchedule(CronScheduleBuilder.cronSchedule("0 13 17 ? 1 ? *"))
    

    Quartz监听

    Quartz的监听器用于当任务调度中你所关注事件发生时,能够及时获取这一事件的通知。类似于任务执行过程中的邮件、短信类的提醒。Quartz监听器主要有JobListener、TriggerListener、SchedulerListener三种,顾名思义,分别表示任务、触发器、调度器对应的监听器。三者的使用方法类似,在开始介绍三种监听器之前,需要明确两个概念:全局监听器与非全局监听器,二者的区别在于:
    全局监听器能够接收到所有的Job/Trigger的事件通知,
    而非全局监听器只能接收到在其上注册的Job或Trigger的事件,不在其上注册的Job或Trigger则不会进行监听。

    1.JobListener
    public class MyJobListener implements JobListener {
        //获取listener的名称
        @Override
        public String getName() {
            String name = getClass().getSimpleName();
            return name;
        }
        //Scheduler在JobDetail将要被执行时调用这个方法
        @Override
        public void jobToBeExecuted(JobExecutionContext context) {
            String jobName = context.getJobDetail().getKey().getName();
            System.out.println("Job的名称是:"+jobName+"Scheduler在JobDetail将要被执行时调用这个方法");
        }
        //Scheduler在JobDetail即将被执行,但又被TriggerListerner否决时会调用该方法
        @Override
        public void jobExecutionVetoed(JobExecutionContext context) {
            String jobName = context.getJobDetail().getKey().getName();
            System.out.println("Job的名称是:"+jobName+"Scheduler在JobDetail即将被执行,但又被TriggerListerner否决时会调用该方法");
        }
        //Scheduler在JobDetail被执行之后调用这个方法
        @Override
        public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
            String jobName = context.getJobDetail().getKey().getName();
            System.out.println("Job的名称是:"+jobName+"Scheduler在JobDetail被执行之后调用这个方法");
        }
    }
    
    2.TriggerListener
    public class MyTriggerListener implements TriggerListener {
    
        private String name;
    
        public MyTriggerListener(String name) {
            this.name = name;
        }
    
        @Override
        public String getName() {
            return name;
        }
        //triggerFired方法:当与监听器相关联的Trigger被触发,Job上的execute()方法将被执行时,Scheduler就调用该方法。
        @Override
        public void triggerFired(Trigger trigger, JobExecutionContext context) {
            String triggerName = trigger.getKey().getName();
            System.out.println(triggerName + " 被触发");
        }
        //vetoJobExecution方法:在 Trigger 触发后,Job 将要被执行时由 Scheduler
        // 调用这个方法。TriggerListener 给了一个选择去否决 Job 的执行。
        // 假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行。
        @Override
        public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
            String triggerName = trigger.getKey().getName();
            System.out.println(triggerName + " 没有被触发");
            return true; // true:表示不会执行Job的方法
        }
        //triggerMisfired方法:Scheduler 调用这个方法是在 Trigger 错过触发时。
        // 你应该关注此方法中持续时间长的逻辑:在出现许多错过触发的 Trigger 时,长逻辑会导致骨牌效应。你应当保持这上方法尽量的小。
        @Override
        public void triggerMisfired(Trigger trigger) {
            String triggerName = trigger.getKey().getName();
            System.out.println(triggerName + " 错过触发");
        }
        //triggerComplete方法:Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法。
        @Override
        public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
            String triggerName = trigger.getKey().getName();
            System.out.println(triggerName + " 完成之后触发");
        }
    }
    
    3.SchedulerListener(重写的方法很多,我也不清楚怎么使用)
    1) jobScheduled方法:用于部署JobDetail时调用
    
    2) jobUnscheduled方法:用于卸载JobDetail时调用
    
    3) triggerFinalized方法:当一个 Trigger 来到了再也不会触发的状态时调用这个方法。除非这个 Job 已设置成了持久性,否则它就会从 Scheduler 中移除。
    
    4) triggersPaused方法:Scheduler 调用这个方法是发生在一个 Trigger 或 Trigger 组被暂停时。假如是 Trigger 组的话,triggerName 参数将为 null。
    
    5) triggersResumed方法:Scheduler 调用这个方法是发生成一个 Trigger 或 Trigger 组从暂停中恢复时。假如是 Trigger 组的话,假如是 Trigger 组的话,triggerName 参数将为 null。参数将为 null。
    6) jobsPaused方法:当一个或一组 JobDetail 暂停时调用这个方法。
    7) jobsResumed方法:当一个或一组 Job 从暂停上恢复时调用这个方法。假如是一个 Job 组,jobName 参数将为 null。
    8) schedulerError方法:在 Scheduler 的正常运行期间产生一个严重错误时调用这个方法。
    9) schedulerStarted方法:当Scheduler 开启时,调用该方法
    10) schedulerInStandbyMode方法: 当Scheduler处于StandBy模式时,调用该方法
    11) schedulerShutdown方法:当Scheduler停止时,调用该方法
    12) schedulingDataCleared方法:当Scheduler中的数据被清除时,调用该方法。
    
    public class MySchedulerListener implements SchedulerListener {
        @Override
        public void jobScheduled(Trigger trigger) {
    
        }
    
        @Override
        public void jobUnscheduled(TriggerKey triggerKey) {
    
        }
    
        @Override
        public void triggerFinalized(Trigger trigger) {
    
        }
    
        @Override
        public void triggerPaused(TriggerKey triggerKey) {
    
        }
    
        @Override
        public void triggersPaused(String triggerGroup) {
    
        }
    
        @Override
        public void triggerResumed(TriggerKey triggerKey) {
    
        }
    
        @Override
        public void triggersResumed(String triggerGroup) {
    
        }
    
        @Override
        public void jobAdded(JobDetail jobDetail) {
    
        }
    
        @Override
        public void jobDeleted(JobKey jobKey) {
    
        }
    
        @Override
        public void jobPaused(JobKey jobKey) {
    
        }
    
        @Override
        public void jobsPaused(String jobGroup) {
    
        }
    
        @Override
        public void jobResumed(JobKey jobKey) {
    
        }
    
        @Override
        public void jobsResumed(String jobGroup) {
    
        }
    
        @Override
        public void schedulerError(String msg, SchedulerException cause) {
    
        }
    
        @Override
        public void schedulerInStandbyMode() {
    
        }
    
        @Override
        public void schedulerStarted() {
    
        }
    
        @Override
        public void schedulerStarting() {
    
        }
    
        @Override
        public void schedulerShutdown() {
    
        }
    
        @Override
        public void schedulerShuttingdown() {
    
        }
    
        @Override
        public void schedulingDataCleared() {
    
        }
    }
    

    Quartz.properties

    默认路径:quartz-2.3.0中的org.quartz中的quartz.properties

    我们也可以在项目的资源下添加quartz.properties文件,去覆盖底层的配置文件。

    组成部分

    • 调度器属性

    org.quartz.scheduler.instanceName属性用来区分特定的调度器实例,可以按照功能用途来给调度器起名。

    org.quartz.scheduler.instanceId属性和前者一样,也允许任何字符串,但这个值必须在所有调度器实例中是唯一的,尤其是在一个集群环境中,作为集群的唯一key。假如你想Quartz帮你生成这个值的话,可以设置为AUTO。

    • 线程池属性

    threadCount

    处理Job的线程个数,至少为1,但最多的话最好不要超过100,在多数机器上设置该值超过100的话就会显得相当不实用了,特别是在你的 Job 执行时间较长的情况下

    threadPriority

    线程的优先级,优先级别高的线程比级别低的线程优先得到执行。最小为1,最大为10,默认为5

    org.quartz.threadPool.class

    #===============================================================     
    	#Configure Main Scheduler Properties     调度器属性
    	#===============================================================  
    	#调度器的实例名     
    	org.quartz.scheduler.instanceName = QuartzScheduler     
    	#调度器的实例ID,大多数情况设置为auto即可  
    	org.quartz.scheduler.instanceId = AUTO     
    	 ====================================     
    	#Configure JobStore 作业存储设置
    	#===============================================================      
    	#要使 Job 存储在内存中需通过设置  org.quartz.jobStrore.class 属性为 org.quartz.simpl.RAMJobStore 
    	org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore     
    	 
    	#===============================================================     
    	#Configure Plugins    插件配置 
    	#===============================================================       
    	org.quartz.plugin.jobInitializer.class =       
    	org.quartz.plugins.xml.JobInitializationPlugin       
    	      
    	org.quartz.plugin.jobInitializer.overWriteExistingJobs = true      
    	org.quartz.plugin.jobInitializer.failOnFileNotFound = true      
    	org.quartz.plugin.jobInitializer.validating=false  
    
    	#===============================================================     
    	#Configure ThreadPool     线程池属性
    	#===============================================================   
    	#处理Job的线程个数,至少为1,但最多的话最好不要超过100,在多数机器上设置该值超过100的话就会显得相当不实用了,特别是在你的 Job 执行时间较长的情况下
    	org.quartz.threadPool.threadCount =  5     
    	#线程的优先级,优先级别高的线程比级别低的线程优先得到执行。最小为1,最大为10,默认为5
    	org.quartz.threadPool.threadPriority = 5 
    	#一个实现了 org.quartz.spi.ThreadPool 接口的类,Quartz 自带的线程池实现类是 org.quartz.smpl.SimpleThreadPool      
    	org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool     
    	 
    	#===========================
    

    基本功能测试完成,下面可以学习一下spring整合quartz

    展开全文
  • 文章目录1.Spring整合Quartz 1.Spring整合Quartz a、quartz调度框架是有内置表的 进入quartz的官网http://www.quartz-scheduler.org/,点击Downloads, 下载后在目录\docs\dbTables下有常用数据库创建quartz表的脚本...
  • 文章目录Quartz理解Quartz简介Quartz运行环境Quartz的设计模式Quartz的核心概念Quartz的体系结构Quartz的常用APIQuartz应用Job和JobDeatilJobExecutionContextJobDataMap有状态的Job和无状态的...
  • Quartz-中断正在执行的任务

    万次阅读 2017-11-15 21:19:17
    示例源码概述由于业务需要,停止Quartz中正在执行的任务 任务类只需要实现InterruptableJob类,然后实现interrupt()方法。 在这个方法中进行标记的改变,在执行中进行这个标记判断,就可实现中断任务了 另外在调度器...
  • Quarzt是一个项目中定时执行任务的开源项目,Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用,这里我们介绍和spring整合的例子 因为Spring已经...
  • `Quartz是纯Java实现`,而且作为`Spring的默认调度框架`,由于Quartz的强大的调度功能、灵活的使用方式、还具有`分布式集群能力`,可以说Quartz出马,可以搞定一切定时任务调度! **核心概念:** - **Job表示实现...
  • 在某些应用场景下要求任务必须具备高可用性和可扩展性,单台服务器不能满足业务需求,这时就需要使用Quartz实现分布式定时任务。 一、分布式任务应用场景 定时任务系统在应用平台中的重要性不言而喻,特别是互联网...
  • 1.1.1 在项目启动类添加@EnableSchedulling开启定时任务管理 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.spring...
  • 默认Quartz的触发器,调度器,任务等信息都是放在内存中的,叫做 RAMJobStore。 好处是快速,坏处是一旦系统重启,那么信息就丢失了,就得全部从头来过。 所以Quartz还提供了另一个方式,可以把这些信息存放在数据库...
  • 一、了解Quartz Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制,可以与 J2EE与 ...
  • Java Quartz实现定时任务

    万次阅读 2018-08-21 16:58:58
    1.pom配置 &amp;amp;amp;amp;amp;lt;dependency&amp;amp;amp;amp;...org.quartz-scheduler&amp;amp;amp;amp;amp;lt;/groupId&amp;amp;amp;amp;amp;gt; &amp;amp;amp;amp;a
  • Spring Boot 整合 Quartz 实现定时任务集群 文章参考 https://blog.csdn.net/l18848956739/article/details/86597709 https://blog.csdn.net/xibei19921101/article/details/105012749 ...
  • 由于公司定时任务是使用的是Quartz,所以我们首先对Quartz的监控管理平台进行了预研。 这里先说一下最后的结果,选用的是xxl-job替代Quartz。期间还预研了elastic-job,在多方对比之下选择了使用xxl-job替换Quartz。...
  • 使用Quartz实现分布式集群任务调度

    千次阅读 2020-01-06 21:37:57
    上一篇博客讲了io流的...交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。 java.io包的好处是代码比较简单、...
  • 1 需求在我的前后端分离的实验室管理项目中,有一个功能是学生状态统计。我的设计是按天统计每种状态的比例。为了便于计算,在每天0点,系统需要将学生的状态...在网上略做搜索以后,我选择了比较流行的定时任务框架...
  • 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。另外,作为 Spring 默认的调度框架,Quartz 很容易与 ...
  • SpringBoot中实现定时任务(Quartz)

    千次阅读 2020-11-26 16:32:15
    Spring 3.0以后自带了 task 调度工具,使用比 Quartz简单方便,使用@Scheduled 注解。 1、创建一个 SpringBoot项目,引入spring-boot-starter-web依赖。 <dependency> <groupId>org.spring...
  • Quartz任务调度——快速入门

    多人点赞 2022-03-09 14:09:15
    简单来说,quartz就是基于java实现的任务调度框架,用于执行你想要的任何任务。 二、Quartz运行环境 Quartz可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行 三、Quar
  • 一、实现流程: 导入依赖: <!-- ...org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version>
  • 前言:现在项目中定时任务一般都使用Quartz框架,Java虽然提供了Timer类但是一般都不用. Spring Boot整合了Quartz框架,用起来很方便,在此记录一下基本配置和实现. 环境:Windows10+JDK8+Spring Boot+Mysql8+Mybatis-...
  • Quartz 定时任务

    2022-05-25 16:39:29
    Quartz部署了多个节点,为啥总是在特定节点执行 Quartz集群 使用数据库来进行集群的管理 QRTZ_SCHEDULER_STATE表用来存储各个实例,如下:前两个是运行在同一台Linux上的两个jar包。第三个是运行在一台windows上的...
  • Quartz框架是一个全功能、开源的...只是经过Quartz封装之后,可以完成更为复杂的任务调度。如果您的应用程序有需要在特定时间执行的任务,或者您的系统有经常性需要维护的工作,那么Quartz可能是您的理想解决方案。Qua
  • Quartz完成定时任务分布式单节点持久化】中我们已经完成任务的持久化,当我们创建一个任务任务会被quartz定时任务框架自动持久化到数据库,我们采用的是SpringBoot项目托管的dataSource来完成的数据源提供,...
  • Quartz定时任务过期处理策略

    千次阅读 2019-04-23 16:46:02
    由于某种原因,例如应用停掉,导致定时任务错过了本该执行的时间点,这就是定时任务过期。对于过期的定时任务,我们需要基于某种策略对其进行处理。 过期策略 在Trigger接口中定义了两种过期策略, public static ...
  • } } 好了,大功告成,一个简单的动态配置的定时任务已经完成。是不是so easy,下面我们再来简单实现下其他的几种常用的api吧。 暂停一个job public void pauseJob(TaskDO task) throws SchedulerException { ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,630
精华内容 3,052
关键字:

quartz查看已完成任务